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?
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?
Câu trả lời:
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,
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 đó OSSpinLock
cũng bị phản đối trên macOS.
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
& mutexes
có 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.
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
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.
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).
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!