Câu trả lời:
Spinlock và semaphore khác nhau chủ yếu ở bốn điểm:
1. Chúng là gì
Một spinlock là một trong những khả năng thực hiện một khóa, cụ thể là một khóa được thực hiện bằng cách chờ bận ("quay"). Semaphore là sự tổng quát hóa của một khóa (hoặc, ngược lại, khóa là một trường hợp đặc biệt của semaphore). Thông thường, nhưng không nhất thiết , các spinlock chỉ hợp lệ trong một quy trình trong khi các semaphores cũng có thể được sử dụng để đồng bộ hóa giữa các quy trình khác nhau.
Một khóa hoạt động để loại trừ lẫn nhau, đó là một chuỗi tại một thời điểm có thể nhận được khóa và tiếp tục với "phần quan trọng" của mã. Thông thường, điều này có nghĩa là mã sửa đổi một số dữ liệu được chia sẻ bởi một số luồng.
Một semaphore có một bộ đếm và sẽ cho phép chính nó được thu nhận bởi một hoặc một số luồng, tùy thuộc vào giá trị bạn đăng lên nó và (trong một số triển khai) tùy thuộc vào giá trị cho phép tối đa của nó là bao nhiêu.
Trong phạm vi, người ta có thể coi khóa là một trường hợp đặc biệt của semaphore với giá trị lớn nhất là 1.
2. Họ làm gì
Như đã nói ở trên, spinlock là một khóa, và do đó là một cơ chế loại trừ lẫn nhau (đúng 1 chọi 1). Nó hoạt động bằng cách liên tục truy vấn và / hoặc sửa đổi vị trí bộ nhớ, thường là theo cách nguyên tử. Điều này có nghĩa là việc có được một spinlock là một hoạt động "bận rộn" có thể đốt cháy chu kỳ CPU trong một thời gian dài (có thể là mãi mãi!) Trong khi hiệu quả đạt được là "không có gì".
Động cơ chính cho cách tiếp cận như vậy là thực tế là một công tắc ngữ cảnh có chi phí tương đương với việc quay một vài trăm (hoặc có thể hàng nghìn) lần, vì vậy nếu có thể có được một khóa bằng cách ghi một vài chu kỳ quay, thì điều này nói chung có thể rất tốt. hiệu quả hơn. Ngoài ra, đối với các ứng dụng thời gian thực, có thể không chấp nhận được việc chặn và chờ trình lập lịch quay lại với chúng vào một thời điểm xa trong tương lai.
Ngược lại, một semaphore hoặc hoàn toàn không quay, hoặc chỉ quay trong một thời gian rất ngắn (như một cách tối ưu hóa để tránh chi phí syscall). Nếu không thể lấy được semaphore, nó sẽ chặn, nhường thời gian của CPU cho một luồng khác đã sẵn sàng chạy. Tất nhiên, điều này có thể có nghĩa là một vài mili giây trôi qua trước khi luồng của bạn được lên lịch lại, nhưng nếu điều này không có vấn đề gì (thường là không) thì đó có thể là một cách tiếp cận rất hiệu quả, tiết kiệm CPU.
3. Cách chúng hoạt động khi có tắc nghẽn
Đó là một quan niệm sai lầm phổ biến rằng các thuật toán spinlock hoặc không có khóa "thường nhanh hơn" hoặc chúng chỉ hữu ích cho "các nhiệm vụ rất ngắn" (lý tưởng là không nên giữ đối tượng đồng bộ hóa lâu hơn hơn mức hoàn toàn cần thiết).
Một sự khác biệt quan trọng là cách các phương pháp tiếp cận khác nhau hoạt động khi có tắc nghẽn .
Một hệ thống được thiết kế tốt thường có mức độ tắc nghẽn thấp hoặc không có (điều này có nghĩa là không phải tất cả các luồng đều cố gắng lấy khóa cùng một lúc). Ví dụ: một người thường sẽ không viết mã có được khóa, sau đó tải nửa megabyte dữ liệu nén zip từ mạng, giải mã và phân tích cú pháp dữ liệu và cuối cùng sửa đổi tham chiếu được chia sẻ (nối dữ liệu vào vùng chứa, v.v.) trước khi nhả khóa. Thay vào đó, người ta sẽ có được khóa chỉ với mục đích truy cập tài nguyên được chia sẻ .
Vì điều này có nghĩa là có nhiều công việc bên ngoài phần quan trọng hơn bên trong nó, nên đương nhiên khả năng một luồng nằm bên trong phần quan trọng là tương đối thấp, và do đó ít luồng đang cạnh tranh cho khóa cùng một lúc. Tất nhiên, thỉnh thoảng hai luồng sẽ cố gắng lấy khóa cùng một lúc (nếu điều này không thể xảy ra, bạn sẽ không cần khóa!), Nhưng đây là ngoại lệ thay vì quy tắc trong một hệ thống "lành mạnh" .
Trong trường hợp như vậy, spinlock vượt trội hơn nhiều so với semaphore vì nếu không có tắc nghẽn khóa, chi phí để có được spinlock chỉ là một chục chu kỳ so với hàng trăm / nghìn chu kỳ đối với một công tắc ngữ cảnh hoặc 10-20 triệu chu kỳ nếu mất phần còn lại của một lát thời gian.
Mặt khác, do tắc nghẽn cao hoặc nếu khóa được giữ trong thời gian dài (đôi khi bạn không thể giúp được!), Spinlock sẽ đốt cháy một lượng lớn chu kỳ CPU mà không đạt được gì.
Một semaphore (hoặc mutex) là một lựa chọn tốt hơn nhiều trong trường hợp này, vì nó cho phép một luồng khác chạy các tác vụ hữu ích trong thời gian đó. Hoặc, nếu không có luồng nào khác có điều gì đó hữu ích để làm, nó sẽ cho phép hệ điều hành giảm tốc độ CPU và giảm nhiệt / tiết kiệm năng lượng.
Ngoài ra, trên một hệ thống đơn lõi, một spinlock sẽ khá hiệu quả trong sự hiện diện của tình trạng tắc nghẽn khóa, như một sợi spinning sẽ lãng phí chờ đợi thời gian hoàn thành của mình cho một sự thay đổi trạng thái đó không thể nào xảy ra (không cho đến khi thread phát hành dự kiến, mà isn 'không xảy ra khi chuỗi chờ đang chạy!). Do đó, với bất kỳ sự tranh cãi nào, việc có được khóa mất khoảng 1 1/2 thời gian trong trường hợp tốt nhất (giả sử luồng phát hành là luồng tiếp theo đang được lên lịch), đây không phải là hành vi tốt.
4. Cách chúng được triển khai
Một semaphore ngày nay thường sys_futex
nằm trong Linux (tùy chọn với một spinlock thoát ra sau một vài lần thử).
Một spinlock thường được thực hiện bằng các phép toán nguyên tử và không sử dụng bất kỳ thứ gì do hệ điều hành cung cấp. Trước đây, điều này có nghĩa là sử dụng bản chất của trình biên dịch hoặc hướng dẫn của trình hợp dịch không di động. Trong khi đó, cả C ++ 11 và C11 đều có các hoạt động nguyên tử như một phần của ngôn ngữ, vì vậy ngoài khó khăn chung là viết mã không khóa chính xác có thể chứng minh, giờ đây có thể triển khai mã không khóa trong một hoàn toàn di động và (gần như) cách không đau.
rất đơn giản, semaphore là đối tượng đồng bộ hóa "năng suất", spinlock là đối tượng 'bận chờ đợi'. (có một chút nữa đối với semaphores là chúng đồng bộ hóa một số luồng, không giống như mutex hoặc bảo vệ hoặc giám sát hoặc phần quan trọng bảo vệ vùng mã khỏi một luồng duy nhất)
Bạn sẽ sử dụng một semaphore trong nhiều trường hợp hơn, nhưng hãy sử dụng một spinlock mà bạn sẽ khóa trong một thời gian rất ngắn - sẽ có chi phí cho việc khóa, đặc biệt nếu bạn khóa nhiều. Trong những trường hợp như vậy, có thể hiệu quả hơn nếu quay khóa trong một thời gian chờ đợi tài nguyên được bảo vệ được mở khóa. Rõ ràng là có một cú đánh hiệu suất nếu bạn quay quá lâu.
thường nếu bạn quay lâu hơn lượng tử luồng, thì bạn nên sử dụng semaphore.
Hơn và trên những gì Yoav Aviram và gbjbaanb đã nói, điểm mấu chốt khác trước đây là bạn sẽ không bao giờ sử dụng khóa quay trên một máy CPU đơn, trong khi semaphore sẽ có ý nghĩa trên một máy như vậy. Ngày nay, bạn thường gặp khó khăn trong việc tìm một máy không có nhiều lõi, siêu phân luồng, hoặc tương đương, nhưng trong trường hợp bạn chỉ có một CPU, bạn nên sử dụng semaphores. (Tôi tin rằng lý do là rõ ràng. Nếu một CPU đang bận chờ thứ gì đó khác giải phóng khóa quay, nhưng nó đang chạy trên CPU duy nhất, khóa sẽ khó có thể được giải phóng cho đến khi quy trình hoặc luồng hiện tại được ưu tiên bởi O / S, có thể mất một lúc và không có gì hữu ích xảy ra cho đến khi quyền ưu tiên xảy ra.)
Tôi không phải là chuyên gia về hạt nhân nhưng đây là một vài điểm:
Ngay cả máy đơn xử lý cũng có thể sử dụng khóa quay nếu quyền ưu tiên hạt nhân được bật trong khi biên dịch hạt nhân. Nếu quyền ưu tiên hạt nhân bị vô hiệu hóa thì spin-lock (có lẽ) sẽ mở rộng thành câu lệnh void .
Ngoài ra, khi chúng tôi đang cố gắng so sánh Semaphore và Spin-lock, tôi tin rằng semaphore đề cập đến cái được sử dụng trong nhân - KHÔNG phải cái được sử dụng cho IPC (userland).
Về cơ bản, khóa quay sẽ được sử dụng nếu phần quan trọng nhỏ (nhỏ hơn phần chi phí của giấc ngủ / thức dậy) và phần quan trọng không gọi bất kỳ thứ gì có thể ngủ! Một semaphore sẽ được sử dụng nếu phần quan trọng lớn hơn và nó có thể ngủ.
Raman Chalotra.
Spinlock đề cập đến việc triển khai khóa liên luồng bằng cách sử dụng các hướng dẫn lắp ráp phụ thuộc vào máy (chẳng hạn như kiểm tra và thiết lập). Nó được gọi là spinlock bởi vì luồng chỉ đơn giản là đợi trong một vòng lặp ("spin") liên tục kiểm tra cho đến khi khóa khả dụng (chờ bận). Spinlock được sử dụng để thay thế cho mutexes, là một cơ sở được cung cấp bởi hệ điều hành (không phải CPU), vì spinlock hoạt động tốt hơn, nếu bị khóa trong một khoảng thời gian ngắn.
Semaphor là một cơ sở được cung cấp bởi các hệ điều hành cho IPC, vì vậy mục đích chính của nó là giao tiếp giữa các quá trình. Là một thiết bị được cung cấp bởi hệ điều hành, hiệu suất của nó sẽ không tốt bằng một spinlock để khóa giữa các sợi (mặc dù có thể). Semaphores tốt hơn để khóa trong thời gian dài hơn.
Điều đó nói lên rằng - việc triển khai các splinlocks trong lắp ráp là một việc phức tạp và không cơ động.
Tôi muốn thêm các quan sát của mình, chung chung hơn và không cụ thể lắm về Linux.
Tùy thuộc vào kiến trúc bộ nhớ và khả năng của bộ xử lý, bạn có thể cần một spin-lock để triển khai semaphore trên hệ thống đa lõi hoặc đa xử lý, bởi vì trong các hệ thống như vậy, điều kiện chạy đua có thể xảy ra khi hai hoặc nhiều luồng / quy trình muốn để có được một semaphore.
Có, nếu kiến trúc bộ nhớ của bạn cung cấp việc khóa phần bộ nhớ bởi một lõi / bộ xử lý làm trì hoãn tất cả các truy cập khác và nếu bộ xử lý của bạn cung cấp thử nghiệm và thiết lập, bạn có thể triển khai semaphore mà không cần khóa quay (nhưng rất cẩn thận! ).
Tuy nhiên, vì các hệ thống đa lõi đơn giản / rẻ tiền được thiết kế (tôi đang làm việc trong các hệ thống nhúng), không phải tất cả các kiến trúc bộ nhớ đều hỗ trợ các tính năng đa lõi / đa xử lý như vậy, chỉ kiểm tra và thiết lập hoặc tương đương. Sau đó, một triển khai có thể như sau:
Việc giải phóng semaphore sẽ cần được thực hiện như sau:
Có, và đối với các semaphores nhị phân đơn giản ở cấp độ hệ điều hành, chỉ có thể sử dụng một spin-lock để thay thế. Nhưng chỉ khi các phần mã được bảo vệ thực sự rất nhỏ.
Như đã nói trước đây, nếu và khi bạn triển khai hệ điều hành của riêng mình, hãy đảm bảo cẩn thận. Gỡ lỗi những lỗi như vậy rất thú vị (ý kiến của tôi, không được nhiều người chia sẻ), nhưng hầu hết là rất tẻ nhạt và khó khăn.
"Mutex" (hoặc "khóa loại trừ lẫn nhau") là một tín hiệu mà hai hoặc nhiều quy trình không đồng bộ có thể sử dụng để dành một tài nguyên được chia sẻ cho mục đích sử dụng riêng. Quá trình đầu tiên có được quyền sở hữu "mutex" cũng có được quyền sở hữu tài nguyên được chia sẻ. Các quy trình khác phải đợi quy trình đầu tiên giải phóng quyền sở hữu của nó đối với "mutex" trước khi họ có thể cố gắng lấy nó.
Khóa nguyên thủy phổ biến nhất trong hạt nhân là spinlock. Khóa quay là một khóa có một ngăn rất đơn giản. Nếu một quy trình cố gắng lấy một khóa quay và nó không khả dụng, quy trình sẽ tiếp tục thử (quay) cho đến khi có thể lấy được khóa. Sự đơn giản này tạo ra một ổ khóa nhỏ và nhanh chóng.
Spinlock được sử dụng nếu và chỉ khi bạn khá chắc chắn rằng kết quả mong đợi của bạn sẽ xảy ra trong thời gian ngắn, trước khi thời gian thực thi của chuỗi của bạn hết hạn.
Ví dụ: Trong mô-đun trình điều khiển thiết bị, Trình điều khiển ghi "0" trong Thanh ghi R0 phần cứng và bây giờ nó cần đợi thanh ghi R0 đó trở thành 1. H / W đọc R0 và thực hiện một số công việc và ghi "1" vào R0. Điều này nói chung là nhanh chóng (trong vài giây). Bây giờ quay tốt hơn nhiều so với đi ngủ và bị gián đoạn bởi H / W. Tất nhiên, trong khi quay, tình trạng hỏng hóc của H / W cần phải được cẩn thận!
Hoàn toàn không có lý do gì để ứng dụng của người dùng quay vòng. Nó không có ý nghĩa. Bạn sẽ xoay vòng cho một số sự kiện xảy ra và sự kiện đó cần được hoàn thành bởi một ứng dụng cấp người dùng khác mà không bao giờ được đảm bảo sẽ xảy ra trong khung thời gian nhanh chóng. Vì vậy, tôi sẽ không quay ở chế độ người dùng. Tốt hơn tôi nên ngủ () hoặc mutexlock () hoặc semaphore lock () ở chế độ người dùng.
Từ sự khác biệt giữa khóa quay và bán vòng là gì? bởi Maciej Piechotka :
Cả hai đều quản lý một nguồn lực hạn chế. Đầu tiên tôi sẽ mô tả sự khác biệt giữa semaphore nhị phân (mutex) và khóa quay.
Khóa spin thực hiện một lần chờ bận - tức là nó tiếp tục chạy vòng lặp:
while (try_acquire_resource ()); ... giải phóng();Nó thực hiện khóa / mở khóa rất nhẹ nhưng nếu luồng khóa sẽ được ưu tiên bởi luồng khác sẽ cố gắng truy cập cùng một nguồn tài nguyên thứ hai thì luồng thứ hai sẽ đơn giản cố gắng xử lý tài nguyên cho đến khi nó cạn kiệt lượng tử CPU.
Mặt khác, mutex hoạt động giống như sau:if (! try_lock ()) { add_to_waiting_queue (); chờ đợi(); } ... process * p = get_next_process_from_waiting_queue (); p-> awUp ();Do đó, nếu chuỗi cố gắng lấy tài nguyên bị chặn, nó sẽ bị tạm ngừng cho đến khi có sẵn. Khóa / mở khóa nặng hơn nhiều nhưng sự chờ đợi là 'miễn phí' và 'công bằng'.
Semaphore là một khóa được phép sử dụng nhiều lần (đã biết từ khi khởi tạo) - ví dụ 3 luồng được phép mô phỏng giữ tài nguyên nhưng không được phép nhiều hơn. Nó được sử dụng ví dụ trong vấn đề nhà sản xuất / người tiêu dùng hoặc nói chung trong hàng đợi:
P (resources_sem) resource = resources.pop () ... resource.push (tài nguyên) V (resources_sem)
spin_trylock
, trả về ngay lập tức với mã lỗi, nếu không thể lấy được khóa. Khóa quay không phải lúc nào cũng khắc nghiệt như vậy. Nhưng việc sử dụngspin_trylock
yêu cầu, đối với một ứng dụng, phải được thiết kế đúng theo cách đó (có thể là một hàng đợi các hoạt động đang chờ xử lý, và ở đây, chọn cái tiếp theo, để lại thực tế trên hàng đợi).