Khi nào nên sử dụng một spinlock thay vì mutex?


294

Tôi nghĩ cả hai đều làm cùng một công việc, làm thế nào để bạn quyết định sử dụng cái nào để đồng bộ hóa?


1
bản sao có thể của Spinlock Versus Semaphore!
Paul R

11
Mutex và Semaphore không giống nhau, vì vậy tôi không nghĩ đây là một bản sao. Câu trả lời của bài viết được tham chiếu nêu chính xác điều này. Để biết thêm chi tiết, hãy xem barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore
nanoquack

Câu trả lời:


729

Học thuyết

Về lý thuyết, khi một luồng cố gắng khóa một mutex và nó không thành công, bởi vì mutex đã bị khóa, nó sẽ chuyển sang chế độ ngủ, ngay lập tức cho phép một luồng khác chạy. Nó sẽ tiếp tục ngủ cho đến khi thức dậy, đó sẽ là trường hợp một khi mutex đang được mở khóa bởi bất kỳ chủ đề nào đang giữ khóa trước đó. Khi một luồng cố gắng khóa một spinlock và nó không thành công, nó sẽ liên tục thử lại khóa nó, cho đến khi cuối cùng nó thành công; do đó, nó sẽ không cho phép một luồng khác thay thế (tuy nhiên, hệ điều hành sẽ mạnh mẽ chuyển sang một luồng khác, một khi lượng tử thời gian chạy CPU của luồng hiện tại đã bị vượt quá, tất nhiên).

Vấn đề

Vấn đề với mutexes là việc đưa các luồng vào trạng thái ngủ và đánh thức chúng lại là cả hai hoạt động khá tốn kém, chúng sẽ cần khá nhiều hướng dẫn CPU và do đó cũng mất một thời gian. Nếu bây giờ mutex chỉ bị khóa trong một khoảng thời gian rất ngắn, thì thời gian để đặt một sợi chỉ để ngủ và đánh thức nó một lần nữa có thể vượt quá thời gian mà sợi chỉ thực sự ngủ rất xa và thậm chí nó có thể vượt quá thời gian của sợi chỉ đã lãng phí bằng cách liên tục bỏ phiếu trên một spinlock. Mặt khác, việc bỏ phiếu trên một spinlock sẽ liên tục lãng phí thời gian của CPU và nếu khóa được giữ trong một thời gian dài hơn, điều này sẽ lãng phí thời gian CPU nhiều hơn và sẽ tốt hơn nhiều nếu thay vào đó là luồng ngủ.

Giải pháp

Sử dụng spinlocks trên hệ thống lõi đơn / CPU đơn thường không có ý nghĩa gì, miễn là việc bỏ phiếu spinlock đang chặn lõi CPU có sẵn duy nhất, không có luồng nào khác có thể chạy và vì không có luồng nào khác có thể chạy, khóa sẽ không được mở khóa IOW, một spinlock chỉ lãng phí thời gian CPU trên các hệ thống đó mà không có lợi ích thực sự. Nếu luồng được đưa vào trạng thái ngủ thay vào đó, một luồng khác có thể đã chạy ngay lập tức, có thể mở khóa và sau đó cho phép luồng đầu tiên tiếp tục xử lý, sau khi nó tỉnh lại.

Trên hệ thống đa lõi / đa CPU, với rất nhiều ổ khóa chỉ được giữ trong một khoảng thời gian rất ngắn, thời gian lãng phí cho việc liên tục đưa các luồng vào trạng thái ngủ và đánh thức chúng một lần nữa có thể làm giảm đáng kể hiệu năng thời gian chạy. Thay vào đó, khi sử dụng spinlocks, các luồng có cơ hội tận dụng lượng tử thời gian chạy đầy đủ của chúng (luôn chỉ chặn trong một khoảng thời gian rất ngắn, nhưng sau đó ngay lập tức tiếp tục công việc của chúng), dẫn đến thông lượng xử lý cao hơn nhiều.

Thực hành

Vì rất thường các lập trình viên không thể biết trước liệu mutexes hoặc spinlocks sẽ tốt hơn (ví dụ: vì số lượng lõi CPU của kiến ​​trúc đích không xác định), nên các hệ điều hành cũng không thể biết liệu một đoạn mã nhất định đã được tối ưu hóa cho lõi đơn hay môi trường đa lõi, hầu hết các hệ thống không phân biệt nghiêm ngặt giữa mutexes và spinlocks. Trong thực tế, hầu hết các hệ điều hành hiện đại đều có đột biến lai và spinlocks lai. Điều đó thực sự có ý nghĩa gì?

Một mutex lai hoạt động giống như một spinlock lúc đầu trên một hệ thống đa lõi. Nếu một luồng không thể khóa mutex, nó sẽ không được đưa vào trạng thái ngủ ngay lập tức, vì mutex có thể được mở khóa khá sớm, vì vậy thay vào đó, mutex trước tiên sẽ hoạt động chính xác như một spinlock. Chỉ khi khóa vẫn chưa được lấy sau một khoảng thời gian nhất định (hoặc thử lại hoặc bất kỳ yếu tố đo lường nào khác), thì sợi chỉ thực sự được đưa vào chế độ ngủ. Nếu cùng một mã chạy trên một hệ thống chỉ có một lõi, thì mutex sẽ không có spinlock, tuy nhiên, như, xem ở trên, điều đó sẽ không có lợi.

Một spinlock lai hoạt động giống như một spinlock bình thường lúc đầu, nhưng để tránh lãng phí quá nhiều thời gian của CPU, nó có thể có một chiến lược dự phòng. Nó thường sẽ không đưa luồng vào chế độ ngủ (vì bạn không muốn điều đó xảy ra khi sử dụng spinlock), nhưng nó có thể quyết định dừng luồng (ngay lập tức hoặc sau một khoảng thời gian nhất định) và cho phép một luồng khác chạy , do đó làm tăng khả năng mở khóa spinlock (một công tắc chủ đề thuần túy thường rẻ hơn so với việc chuyển một chủ đề sang chế độ ngủ và đánh thức nó trở lại sau đó, mặc dù không xa lắm).

Tóm lược

Nếu nghi ngờ, hãy sử dụng mutexes, chúng thường là lựa chọn tốt hơn và hầu hết các hệ thống hiện đại sẽ cho phép chúng quay vòng trong một khoảng thời gian rất ngắn, nếu điều này có vẻ có lợi. Sử dụng spinlocks đôi khi có thể cải thiện hiệu suất, nhưng chỉ trong một số điều kiện nhất định và thực tế là bạn nghi ngờ thay vì nói với tôi, rằng bạn hiện không làm việc với bất kỳ dự án nào mà spinlock có thể có lợi. Bạn có thể cân nhắc sử dụng "đối tượng khóa" của riêng mình, có thể sử dụng spinlock hoặc mutex bên trong (ví dụ: hành vi này có thể được cấu hình khi tạo một đối tượng như vậy), ban đầu sử dụng mutexes ở mọi nơi và nếu bạn nghĩ rằng sử dụng spinlock ở đâu đó có thể thực sự giúp đỡ, hãy dùng thử và so sánh kết quả (ví dụ: sử dụng trình lược tả), nhưng hãy chắc chắn kiểm tra cả hai trường hợp,

Cập nhật: Cảnh báo cho iOS

Trên thực tế không phải iOS cụ thể nhưng iOS là nền tảng mà hầu hết các nhà phát triển có thể gặp phải vấn đề đó: Nếu hệ thống của bạn có bộ lập lịch xử lý luồng, điều đó không đảm bảo rằng bất kỳ luồng nào, dù mức độ ưu tiên của nó có thấp đến đâu, cuối cùng cũng sẽ có cơ hội chạy, sau đó spinlocks có thể dẫn đến bế tắc vĩnh viễn. Bộ lập lịch iOS phân biệt các lớp khác nhau của luồng và luồng trên lớp thấp hơn sẽ chỉ chạy nếu không có luồng nào trong lớp cao hơn muốn chạy. Không có chiến lược dự phòng cho việc này, vì vậy nếu bạn có sẵn các luồng cấp cao, các luồng cấp thấp sẽ không bao giờ có thời gian CPU và do đó không bao giờ có cơ hội thực hiện bất kỳ công việc nào.

Vấn đề xuất hiện như sau: Mã của bạn có được một spinlock trong một luồng cấp thấp và trong khi nó ở giữa khóa đó, lượng tử thời gian đã vượt quá và luồng ngừng chạy. Cách duy nhất làm thế nào spinlock này có thể được phát hành lại là nếu luồng xử lý hạng thấp đó lại có thời gian CPU nhưng điều này không được đảm bảo sẽ xảy ra. Bạn có thể có một vài chủ đề của lớp cao cấp liên tục muốn chạy và trình lập lịch tác vụ sẽ luôn ưu tiên chúng. Một trong số chúng có thể chạy trên spinlock và cố gắng để có được nó, điều đó là không thể, và hệ thống sẽ làm cho nó mang lại hiệu quả. Vấn đề là: Một luồng mang lại ngay lập tức có sẵn để chạy lại! Có một ưu tiên cao hơn luồng giữ khóa, luồng giữ khóa không có cơ hội nhận được thời gian chạy CPU.

Tại sao vấn đề này không xảy ra với mutexes? Khi chủ đề cao cấp không thể có được mutex, nó sẽ không mang lại, nó có thể quay một chút nhưng cuối cùng sẽ được gửi đến giấc ngủ. Một chuỗi ngủ không có sẵn để chạy cho đến khi nó được đánh thức bởi một sự kiện, ví dụ như một sự kiện như mutex được mở khóa, nó đã được chờ đợi. Apple nhận thức được vấn đề đó và do đó đã phản đối OSSpinLock. Khóa mới được gọi os_unfair_lock. Khóa này tránh được tình huống được đề cập ở trên vì nó nhận thức được các lớp ưu tiên luồng khác nhau. Nếu bạn chắc chắn rằng sử dụng spinlocks là một ý tưởng hay trong dự án iOS của bạn, hãy sử dụng dự án đó. Tránh xaOSSpinLock! Và trong mọi trường hợp không thực hiện spinlocks của riêng bạn trong iOS! Nếu nghi ngờ, hãy sử dụng một mutex! macOS không bị ảnh hưởng bởi vấn đề này vì nó có một bộ lập lịch xử lý luồng khác, không cho phép bất kỳ luồng nào (ngay cả các luồng thấp) có thể "chạy khô" trong thời gian CPU, nhưng tình trạng tương tự có thể xảy ra ở đó và sau đó sẽ dẫn đến rất kém hiệu suất, do đó OSSpinLockcũng bị phản đối trên macOS.


3
giải thích tuyệt vời ... Tôi có nghi ngờ về spinlock i, tôi có thể sử dụng spinlock trong ISR không? nếu không tại sao không
haris

4
@Mecki Nếu tôi không nhầm, tôi tin rằng bạn đã gợi ý trong câu trả lời của mình rằng việc cắt thời gian chỉ xảy ra trên các hệ thống xử lý đơn. Điều này LAF không đúng! Bạn có thể sử dụng khóa xoay trên hệ thống xử lý đơn và nó sẽ quay cho đến khi hết lượng tử thời gian. Sau đó, một luồng khác có cùng mức độ ưu tiên có thể đảm nhận (giống như những gì bạn mô tả cho các hệ thống đa bộ xử lý).
fumoboy007

7
@ fumoboy007 "và nó sẽ quay cho đến khi lượng tử thời gian hết hạn" // Điều đó có nghĩa là bạn lãng phí thời gian CPU / năng lượng pin hoàn toàn không có bất kỳ lợi ích nào, hoàn toàn không có lợi. Và không, tôi không nói rằng việc cắt thời gian chỉ xảy ra trên các hệ thống lõi đơn, tôi đã nói trên các hệ thống lõi đơn chỉ có cắt thời gian, trong khi đó có hệ thống song song THỰC SỰ (và cả cắt thời gian, nhưng không liên quan đến những gì tôi đã viết trong Đáp lại); Ngoài ra, bạn hoàn toàn bỏ lỡ quan điểm về một spinlock lai là gì và tại sao nó hoạt động tốt trên các hệ thống đơn và đa lõi.
Mecki

11
@ fumoboy007 Chủ đề A giữ khóa và bị gián đoạn. Chủ đề B chạy và muốn khóa, nhưng không thể lấy được, vì vậy nó sẽ quay. Trên hệ thống đa lõi, Thread A có thể tiếp tục chạy trên lõi khác trong khi Thread B vẫn đang quay, giải phóng khóa và Thread B có thể tiếp tục trong lượng tử thời gian hiện tại. Trên một hệ thống lõi đơn, chỉ có một lõi A có thể chạy để giải phóng khóa và lõi này được giữ bận bằng cách quay Thread B. Vì vậy, không có cách nào spinlock có thể được phát hành trước khi Thread B vượt quá lượng tử thời gian của nó và do đó, tất cả việc quay chỉ là một sự lãng phí thời gian.
Mecki

2
Nếu bạn muốn tìm hiểu thêm về spinlocksmutexes được triển khai trong kernel Linux, tôi khuyên bạn nên đọc chương 5 của Trình điều khiển thiết bị Linux tuyệt vời , Ấn bản thứ ba (LDD3) (mutexes: trang 109; spinlocks: trang 116).
patryk.beza

7

Tiếp tục với đề xuất của Mecki, bài viết này pthread mutex vs pthread spinlock trên blog của Alexander Sandler, Alex trên Linux cho thấy cách spinlock& mutexescó thể được thực hiện để kiểm tra hành vi bằng cách sử dụng #ifdef.

Tuy nhiên, hãy chắc chắn thực hiện cuộc gọi cuối cùng dựa trên sự quan sát, hiểu biết của bạn vì ví dụ đưa ra là một trường hợp riêng biệt, yêu cầu dự án của bạn, môi trường có thể hoàn toàn khác nhau.


6

Cũng xin lưu ý rằng trên các môi trường và điều kiện nhất định (chẳng hạn như chạy trên các cửa sổ ở cấp công văn> = MỨC ĐỘ HIỂN THỊ), bạn không thể sử dụng mutex mà thay vào đó là spinlock. Trên unix - điều tương tự.

Đây là câu hỏi tương đương trên trang web stackexchange unix của đối thủ cạnh tranh: /unix/5107/why-are-spin-locks-good-choices-in-linux-kernel-design-instead-of-s Something- hơn

Thông tin về việc gửi đi trên các hệ thống cửa sổ: http://doad.microsoft.com/doad/e/b/a/eba1050f-a31d-436b-9281-92cdfeae4b45/IRQL_thread.doc


6

Mecki trả lời khá tốt móng tay nó. Tuy nhiên, trên một bộ xử lý, sử dụng một spinlock có thể có ý nghĩa khi tác vụ đang chờ trên khóa được đưa ra bởi một Routine Service Routine. Ngắt sẽ chuyển điều khiển sang ISR, điều này sẽ sẵn sàng tài nguyên để sử dụng cho tác vụ chờ. Nó sẽ kết thúc bằng cách giải phóng khóa trước khi đưa lại quyền kiểm soát cho tác vụ bị gián đoạn. Nhiệm vụ kéo sợi sẽ tìm thấy spinlock có sẵn và tiến hành.


2
Tôi không chắc chắn hoàn toàn đồng ý với câu trả lời này. Một bộ xử lý, nếu một tác vụ giữ khóa tài nguyên, thì ISR không thể tiến hành an toàn và không thể chờ tác vụ mở khóa tài nguyên (vì tác vụ giữ tài nguyên bị gián đoạn). Trong các trường hợp như vậy, tác vụ chỉ cần vô hiệu hóa các ngắt để thực thi loại trừ giữa chính nó và ISR. Tất nhiên, điều này sẽ phải được thực hiện trong khoảng thời gian rất ngắn.
dùng1202136

3

Cơ chế đồng bộ hóa Spinlock và Mutex rất phổ biến ngày nay được nhìn thấy.

Trước tiên hãy nghĩ về Spinlock.

Về cơ bản, đây là một hành động chờ đợi bận rộn, điều đó có nghĩa là chúng ta phải chờ một khóa được chỉ định được phát hành trước khi chúng ta có thể tiến hành hành động tiếp theo. Về mặt khái niệm rất đơn giản, trong khi thực hiện nó không phải là trường hợp. Ví dụ: Nếu khóa chưa được phát hành thì luồng đã được trao đổi và chuyển sang trạng thái ngủ, chúng ta có nên xử lý không? Làm thế nào để đối phó với khóa đồng bộ hóa khi hai luồng đồng thời yêu cầu truy cập?

Nói chung, ý tưởng trực quan nhất là xử lý đồng bộ hóa thông qua một biến để bảo vệ phần quan trọng. Khái niệm về Mutex tương tự nhau, nhưng chúng vẫn khác nhau. Tập trung vào: sử dụng CPU. Spinlock tiêu tốn thời gian của CPU để chờ thực hiện hành động và do đó, chúng ta có thể tổng hợp sự khác biệt giữa hai điều này:

Trong môi trường đa lõi đồng nhất, nếu thời gian dành cho phần quan trọng nhỏ hơn sử dụng Spinlock, vì chúng ta có thể giảm thời gian chuyển đổi ngữ cảnh. (So ​​sánh lõi đơn không quan trọng, vì một số hệ thống triển khai Spinlock ở giữa công tắc)

Trong Windows, sử dụng Spinlock sẽ nâng cấp luồng lên DISPATCH_LEVEL, trong một số trường hợp có thể không được phép, vì vậy lần này chúng tôi phải sử dụng Mutex (APC_LEVEL).


-6

Sử dụng spinlocks trên hệ thống CPU đơn lõi / CPU thường không có ý nghĩa gì, miễn là việc bỏ phiếu spinlock đang chặn lõi CPU có sẵn duy nhất, không có luồng nào khác có thể chạy và vì không có luồng nào khác có thể chạy, khóa sẽ không được mở khóa IOW, một spinlock chỉ lãng phí thời gian CPU trên các hệ thống đó mà không có lợi ích thực sự

Cái này sai. Không có sự lãng phí của các chu kỳ cpu trong việc sử dụng spinlocks trên các hệ thống bộ xử lý uni, bởi vì một khi một quá trình mất khóa quay, quyền ưu tiên bị vô hiệu hóa, do đó, không thể có ai quay! Chỉ là việc sử dụng nó không có ý nghĩa gì cả! Do đó, spinlocks trên các hệ thống Uni được thay thế bằng preeem_disable tại thời điểm biên dịch bởi kernel!


Các trích dẫn vẫn hoàn toàn đúng. Nếu kết quả được biên dịch của mã nguồn không chứa spinlocks, thì trích dẫn là không liên quan. Giả sử những gì bạn nói là đúng về kernel thay thế spinlocks tại thời điểm biên dịch, spinlocks được xử lý như thế nào khi được biên dịch trước trên một máy khác có thể hoặc không phải là bộ xử lý, trừ khi chúng ta chỉ nói đúng về spinlocks trong kernel?
Hydranix

"Một khi quá trình mất khóa quay, quyền ưu tiên bị vô hiệu hóa". Ưu tiên không bị vô hiệu hóa khi một quá trình quay vòng. Nếu đó là trường hợp, một quá trình duy nhất có thể phá hủy toàn bộ máy bằng cách chỉ cần nhập một spinlock và không bao giờ rời đi. Lưu ý rằng nếu luồng của bạn chạy trong không gian kernel (thay vì không gian người dùng), thì việc khóa spin thực sự vô hiệu hóa quyền ưu tiên, nhưng tôi không nghĩ đó là những gì đang được thảo luận ở đây.
Konstantin Weitz

Tại thời gian biên dịch bởi kernel ?
Shien

Khóa quay @konstantin FYI chỉ có thể được thực hiện trong không gian kernel. Và khi khóa quay được thực hiện, quyền ưu tiên sẽ bị vô hiệu hóa trên bộ xử lý cục bộ.
Neelansh Găngal

@hydranix Không hiểu bạn? Rõ ràng bạn không thể biên dịch mô-đun dựa trên kernel trong đó CONFIG_SMP được bật và chạy cùng một mô-đun trên kernel đã tắt CONFIG_SMP.
Neelansh Găngal
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.