Câu trả lời:
Khóa xoay là một cách để bảo vệ tài nguyên được chia sẻ khỏi bị sửa đổi bởi hai hoặc nhiều quy trình cùng một lúc. Quá trình đầu tiên cố gắng sửa đổi tài nguyên "mua lại" khóa và tiếp tục trên đường đi, thực hiện những gì cần thiết với tài nguyên. Bất kỳ quy trình nào khác sau đó cố gắng để có được khóa đều bị dừng; họ được cho là "quay tại chỗ" chờ khóa được phát hành theo quy trình đầu tiên, do đó, tên spin lock.
Nhân Linux sử dụng khóa spin cho nhiều thứ, chẳng hạn như khi gửi dữ liệu đến một thiết bị ngoại vi cụ thể. Hầu hết các thiết bị ngoại vi phần cứng không được thiết kế để xử lý nhiều cập nhật trạng thái đồng thời. Nếu hai sửa đổi khác nhau phải xảy ra, một phải tuân thủ nghiêm ngặt cái kia, chúng không thể trùng nhau. Khóa xoay cung cấp sự bảo vệ cần thiết, đảm bảo rằng các sửa đổi xảy ra cùng một lúc.
Khóa spin là một vấn đề bởi vì các khối quay của lõi CPU không thể thực hiện bất kỳ công việc nào khác. Mặc dù nhân Linux cung cấp các dịch vụ đa nhiệm cho các chương trình không gian người dùng chạy bên dưới, nhưng cơ sở đa nhiệm đa năng đó không mở rộng sang mã hạt nhân.
Tình trạng này đang thay đổi và đã xảy ra đối với hầu hết sự tồn tại của Linux. Cho đến Linux 2.0, hạt nhân gần như hoàn toàn là một chương trình tác vụ đơn lẻ: bất cứ khi nào CPU chạy mã hạt nhân, chỉ có một lõi CPU được sử dụng, bởi vì có một khóa quay duy nhất bảo vệ tất cả các tài nguyên được chia sẻ, được gọi là Khóa hạt nhân lớn (BKL ). Bắt đầu với Linux 2.2, BKL đang dần bị chia thành nhiều khóa độc lập mà mỗi khóa bảo vệ một lớp tài nguyên tập trung hơn. Ngày nay, với kernel 2.6, BKL vẫn tồn tại, nhưng nó chỉ được sử dụng bởi mã thực sự cũ không thể dễ dàng chuyển sang một số khóa chi tiết hơn. Giờ đây, một hộp đa lõi có thể có mọi CPU chạy mã hạt nhân hữu ích.
Có giới hạn đối với tiện ích phá vỡ BKL vì nhân Linux thiếu đa nhiệm chung. Nếu lõi CPU bị chặn quay trên khóa spin kernel, nó không thể được sửa lại, để đi làm điều gì khác cho đến khi khóa được giải phóng. Nó chỉ ngồi và quay cho đến khi khóa được phát hành.
Khóa xoay có hiệu quả có thể biến hộp 16 lõi của quái vật thành hộp đơn lõi, nếu khối lượng công việc lớn đến mức mọi lõi luôn chờ đợi một khóa xoay. Đây là giới hạn chính đối với khả năng mở rộng của nhân Linux: nhân đôi lõi CPU từ 2 lên 4 có thể sẽ tăng gần gấp đôi tốc độ của một hộp Linux, nhưng nhân đôi từ 16 lên 32 có lẽ sẽ không, với hầu hết khối lượng công việc.
Khóa xoay là khi một quá trình liên tục bỏ phiếu để loại bỏ khóa. Nó được coi là xấu vì quá trình này đang tiêu thụ chu kỳ (thường là) không cần thiết. Nó không dành riêng cho Linux, mà là một mẫu lập trình chung. Và trong khi nó thường được coi là một thực tiễn xấu, trên thực tế, đó là giải pháp chính xác; có những trường hợp chi phí sử dụng bộ lập lịch cao hơn (tính theo chu kỳ CPU) so với chi phí của một vài chu kỳ mà spinlock dự kiến sẽ kéo dài.
Ví dụ về spinlock:
#!/bin/sh
#wait for some program to clear a lock before doing stuff
while [ -f /var/run/example.lock ]; do
sleep 1
done
#do stuff
Thường xuyên có một cách để tránh khóa quay. Đối với ví dụ cụ thể này, có một công cụ Linux được gọi là inotifywait (nó thường không được cài đặt theo mặc định). Nếu nó được viết bằng C, bạn chỉ cần sử dụng API inotify mà Linux cung cấp.
Ví dụ tương tự, sử dụng inotifywait cho thấy cách thực hiện điều tương tự mà không cần khóa xoay:
#/bin/sh
inotifywait -e delete_self /var/run/example.lock
#do stuff
Khi một luồng cố gắng để có được một khóa, ba điều có thể xảy ra nếu nó thất bại, nó có thể thử và chặn, nó có thể thử và tiếp tục, nó có thể thử đi ngủ để báo cho hệ điều hành đánh thức nó khi có sự kiện xảy ra.
Bây giờ hãy thử và tiếp tục sử dụng ít thời gian hơn sau đó thử và chặn. Chúng ta hãy nói rằng hiện tại một "thử và tiếp tục" sẽ mất i đơn vị thời gian và một "thử và chặn" sẽ mất một trăm.
Bây giờ chúng ta hãy giả sử rằng trung bình một luồng sẽ mất 4 đơn vị thời gian giữ khóa. Thật lãng phí khi chờ đợi 100 đơn vị. Vì vậy, thay vì bạn viết một vòng lặp "thử và tiếp tục". Trong lần thử thứ tư, bạn thường sẽ có được khóa. Đây là một khóa quay. Nó được gọi như vậy bởi vì sợi chỉ tiếp tục quay tại chỗ cho đến khi nó có được khóa.
Một biện pháp an toàn được thêm vào là giới hạn số lần vòng lặp chạy. Vì vậy, trong ví dụ bạn thực hiện chạy vòng lặp for, ví dụ sáu lần, nếu thất bại thì bạn "thử và chặn".
Nếu bạn biết rằng một luồng sẽ luôn giữ khóa trong 200 đơn vị, thì bạn đang lãng phí thời gian của máy tính cho mỗi lần thử và tiếp tục.
Vì vậy, cuối cùng, một khóa quay có thể rất hiệu quả hoặc lãng phí. Thật lãng phí khi thời gian "thông thường" để giữ khóa cao hơn thời gian để "thử và chặn". Sẽ hiệu quả khi thời gian thông thường để giữ khóa nhỏ hơn nhiều so với thời gian để 'thử và chặn ".
Ps: Cuốn sách cần đọc trên chủ đề là "A Thread Primer", nếu bạn vẫn có thể tìm thấy nó.
Một khóa là một cách để hai hoặc nhiều nhiệm vụ (quy trình, chủ đề) để đồng bộ hóa. Cụ thể, khi cả hai tác vụ cần truy cập không liên tục vào một tài nguyên chỉ có thể được sử dụng bởi một tác vụ tại một thời điểm, đó là cách để các tác vụ sắp xếp không sử dụng tài nguyên cùng một lúc. Để truy cập tài nguyên, một tác vụ phải thực hiện các bước sau:
take the lock
use the resource
release the lock
Không thể khóa nếu không có nhiệm vụ khác đã thực hiện. (Hãy nghĩ về khóa như một đối tượng mã thông báo vật lý. Đối tượng đang ở trong ngăn kéo hoặc ai đó có trong tay. Chỉ có người đang giữ đối tượng mới có thể truy cập tài nguyên.) Vì vậy, người cầm khóa thực sự có nghĩa là chờ đợi cho đến khi không ai khác có khóa, sau đó lấy nó.
Từ quan điểm cấp cao, có hai cách chính để thực hiện khóa: spinlocks và điều kiện. Với spinlocks , lấy khóa có nghĩa là chỉ quay Spin (tức là không làm gì trong vòng lặp) cho đến khi không có ai khác có khóa. Với các điều kiện, nếu một tác vụ cố gắng khóa nhưng bị chặn vì một tác vụ khác giữ nó, người mới sẽ vào hàng chờ; các hoạt động phát hành báo hiệu cho bất kỳ nhiệm vụ chờ đợi mà khóa hiện có sẵn.
(Những giải thích này không đủ để cho phép bạn thực hiện khóa, vì tôi chưa nói gì về nguyên tử. Nhưng nguyên tử không quan trọng ở đây.)
Spinlocks rõ ràng là lãng phí: nhiệm vụ chờ tiếp tục kiểm tra xem khóa có được thực hiện hay không. Vậy tại sao và khi nào nó được sử dụng? Spinlocks thường rất rẻ để có được trong trường hợp khóa không được giữ. Điều này làm cho nó hấp dẫn khi cơ hội cho khóa được tổ chức là nhỏ. Hơn nữa, spinlocks chỉ khả thi nếu có được khóa sẽ không mất nhiều thời gian. Vì vậy, spinlocks có xu hướng được sử dụng trong các tình huống chúng sẽ được giữ trong một thời gian rất ngắn, do đó, hầu hết các nỗ lực được dự kiến sẽ thành công ngay lần thử đầu tiên và những người cần chờ đợi không phải chờ đợi lâu.
Có một lời giải thích tốt về spinlocks và các cơ chế đồng thời khác của nhân Linux trong Trình điều khiển thiết bị Linux , chương 5.
synchronized
sẽ được thực hiện bởi một spinlock: một synchronized
khối có thể chạy trong một thời gian rất dài. synchronized
là một cấu trúc ngôn ngữ để làm cho các khóa dễ sử dụng trong một số trường hợp nhất định, không phải là nguyên thủy để xây dựng các nguyên hàm đồng bộ hóa lớn hơn.
Spinlock là một khóa hoạt động bằng cách vô hiệu hóa bộ lập lịch và có thể làm gián đoạn (biến thể irqsave) trên lõi cụ thể mà khóa được mua. Nó khác với một mutex ở chỗ nó vô hiệu hóa lập lịch để chỉ luồng của bạn có thể chạy trong khi spinlock được giữ. Một mutex cho phép các luồng ưu tiên cao hơn khác được lên lịch trong khi nó được giữ nhưng không cho phép chúng thực hiện đồng thời phần được bảo vệ. Vì spinlocks vô hiệu hóa đa nhiệm, bạn không thể lấy một spinlock và sau đó gọi một số mã khác sẽ cố gắng để có được một mutex. Mã của bạn bên trong phần spinlock không bao giờ được ngủ (mã thường ngủ khi nó gặp một mutex bị khóa hoặc semaphore trống).
Một điểm khác biệt với mutex là các chủ đề thường xếp hàng cho một mutex để một mutex bên dưới có một hàng đợi. Trong khi spinlock chỉ đảm bảo rằng không có luồng nào khác sẽ chạy ngay cả khi nó phải. Do đó, bạn không bao giờ được giữ một spinlock khi gọi các chức năng bên ngoài tệp của bạn mà bạn không chắc chắn sẽ không ngủ.
Khi bạn muốn chia sẻ spinlock của mình với một ngắt, bạn phải sử dụng biến thể irqsave. Điều này sẽ không chỉ vô hiệu hóa lịch trình mà còn vô hiệu hóa các ngắt. Nó có ý nghĩa phải không? Spinlock hoạt động bằng cách đảm bảo không có gì khác sẽ chạy. Nếu bạn không muốn ngắt để chạy, bạn vô hiệu hóa nó và tiến hành an toàn vào phần quan trọng.
Trên máy đa lõi, một spinlock sẽ thực sự quay tròn chờ đợi một lõi khác giữ khóa để giải phóng nó. Việc quay này chỉ xảy ra trên các máy đa lõi vì trên các lõi đơn thì điều đó không thể xảy ra (bạn giữ spinlock và tiếp tục hoặc bạn không bao giờ chạy cho đến khi khóa được giải phóng).
Spinlock không lãng phí khi nó có ý nghĩa. Đối với các phần quan trọng rất nhỏ, sẽ rất lãng phí khi phân bổ hàng đợi nhiệm vụ mutex so với việc chỉ tạm dừng bộ lập lịch trong vài micrô giây để hoàn thành công việc quan trọng. Nếu bạn cần ngủ hoặc giữ khóa trên một thao tác io (có thể ngủ) thì hãy sử dụng mutex. Chắc chắn không bao giờ khóa một spinlock và sau đó cố gắng phát hành nó trong một ngắt. Trong khi điều này sẽ hoạt động, nó sẽ giống như crap arduino của while (flagnotset); trong trường hợp như vậy sử dụng một semaphore.
Lấy một spinlock khi bạn cần loại trừ lẫn nhau đơn giản cho các khối giao dịch bộ nhớ. Lấy một mutex khi bạn muốn nhiều luồng dừng lại ngay trước khóa mutex và sau đó là chuỗi ưu tiên cao nhất được chọn để tiếp tục khi mutex trở nên miễn phí và khi bạn khóa và phát hành trong cùng một luồng. Lấy một semaphore khi bạn có ý định đăng nó trong một chủ đề hoặc một ngắt và đưa nó trong một chủ đề khác. Đó là ba cách hơi khác nhau để đảm bảo loại trừ lẫn nhau và chúng được sử dụng cho các mục đích hơi khác nhau.