Câu trả lời:
Các ổ khóa được sử dụng để loại trừ lẫn nhau. Khi bạn muốn đảm bảo rằng một đoạn mã là nguyên tử, hãy đặt một chiếc khóa xung quanh nó. Về mặt lý thuyết, bạn có thể sử dụng một semaphore nhị phân để làm điều này, nhưng đó là một trường hợp đặc biệt.
Semaphores và các biến điều kiện xây dựng trên cơ sở loại trừ lẫn nhau được cung cấp bởi các khóa và được sử dụng để cung cấp quyền truy cập đồng bộ vào các tài nguyên được chia sẻ. Chúng có thể được sử dụng cho các mục đích tương tự.
Một biến điều kiện thường được sử dụng để tránh việc chờ đợi bận rộn (lặp đi lặp lại trong khi kiểm tra một điều kiện) trong khi chờ tài nguyên sẵn có. Ví dụ: nếu bạn có một chuỗi (hoặc nhiều chuỗi) không thể tiếp tục trở đi cho đến khi hàng đợi trống, thì cách tiếp cận chờ bận sẽ là chỉ thực hiện một số việc như:
//pseudocode
while(!queue.empty())
{
sleep(1);
}
Vấn đề với điều này là bạn đang lãng phí thời gian của bộ xử lý bằng cách để luồng này liên tục kiểm tra điều kiện. Thay vào đó, tại sao không có một biến đồng bộ hóa có thể được báo hiệu để báo cho luồng biết rằng tài nguyên có sẵn?
//pseudocode
syncVar.lock.acquire();
while(!queue.empty())
{
syncVar.wait();
}
//do stuff with queue
syncVar.lock.release();
Có lẽ, bạn sẽ có một luồng ở đâu đó khác đang kéo mọi thứ ra khỏi hàng đợi. Khi hàng đợi trống, nó có thể gọi syncVar.signal()
để đánh thức một chuỗi ngẫu nhiên đang ở chế độ ngủ syncVar.wait()
(hoặc thường cũng có một signalAll()
hoặc broadcast()
phương thức để đánh thức tất cả các chuỗi đang chờ).
Tôi thường sử dụng các biến đồng bộ hóa như thế này khi tôi có một hoặc nhiều luồng đang chờ trên một điều kiện cụ thể (ví dụ: hàng đợi trống).
Semaphores có thể được sử dụng tương tự, nhưng tôi nghĩ rằng chúng được sử dụng tốt hơn khi bạn có một tài nguyên được chia sẻ có thể có sẵn và không có sẵn dựa trên một số số nguyên của những thứ có sẵn. Semaphores phù hợp với các tình huống của nhà sản xuất / người tiêu dùng khi nhà sản xuất đang phân bổ nguồn lực và người tiêu dùng đang tiêu thụ chúng.
Hãy nghĩ xem bạn có máy bán nước ngọt tự động không. Chỉ có một máy làm nước ngọt và đó là tài nguyên dùng chung. Bạn có một chủ đề là nhà cung cấp (nhà sản xuất) chịu trách nhiệm giữ máy và N luồng là người mua (người tiêu dùng) muốn lấy nước ngọt ra khỏi máy. Số lượng nước ngọt trong máy là giá trị số nguyên sẽ điều khiển semaphore của chúng ta.
Mọi chủ đề của người mua (người tiêu dùng) đến với máy làm nước ngọt gọi down()
phương pháp semaphore để lấy một cốc nước ngọt. Thao tác này sẽ lấy một lon nước ngọt từ máy và giảm số lượng nước ngọt có sẵn đi 1. Nếu có nước ngọt, mã sẽ tiếp tục chạy qua down()
câu lệnh mà không có vấn đề gì. Nếu không có nước ngọt có sẵn, chuỗi sẽ ngủ ở đây để chờ được thông báo về thời điểm có nước ngọt trở lại (khi có thêm nước ngọt trong máy).
Chủ đề của nhà cung cấp (nhà sản xuất) về cơ bản sẽ đợi máy làm soda trống. Nhà cung cấp sẽ nhận được thông báo khi nước ngọt cuối cùng được lấy ra khỏi máy (và một hoặc nhiều người tiêu dùng đang chờ lấy nước ngọt ra). Nhà cung cấp sẽ trang bị lại máy làm nước ngọt bằng up()
phương pháp semaphore , số lượng nước ngọt có sẵn sẽ được tăng lên mỗi lần và do đó các chủ đề của người tiêu dùng đang chờ đợi sẽ được thông báo rằng có nhiều nước ngọt hơn.
Các phương thức wait()
và signal()
phương thức của một biến đồng bộ hóa có xu hướng bị ẩn bên trong down()
và các up()
hoạt động của semaphore.
Chắc chắn có sự trùng lặp giữa hai sự lựa chọn. Có nhiều trường hợp mà một semaphore hoặc một biến điều kiện (hoặc tập hợp các biến điều kiện) đều có thể phục vụ mục đích của bạn. Cả semaphores và biến điều kiện đều được liên kết với một đối tượng khóa mà chúng sử dụng để duy trì loại trừ lẫn nhau, nhưng sau đó chúng cung cấp chức năng bổ sung trên đầu khóa để đồng bộ hóa việc thực thi luồng. Phần lớn tùy thuộc vào bạn để tìm ra cách nào phù hợp nhất với tình huống của bạn.
Đó không nhất thiết là mô tả kỹ thuật nhất, nhưng đó là cách nó có ý nghĩa trong đầu tôi.
Hãy tiết lộ những gì dưới mui xe.
Biến có điều kiện về cơ bản là một hàng đợi , hỗ trợ các hoạt động chặn-đợi và đánh thức, tức là bạn có thể đặt một luồng vào hàng đợi và đặt trạng thái của nó thành BLOCK, và lấy một luồng ra khỏi nó và đặt trạng thái của nó thành SN SÀNG.
Lưu ý rằng để sử dụng một biến có điều kiện, cần có hai phần tử khác:
Giao thức sau đó trở thành,
Semaphore thực chất là một bộ đếm + một mutex + một hàng đợi. Và nó có thể được sử dụng như nó vốn có mà không cần phụ thuộc vào bên ngoài. Bạn có thể sử dụng nó dưới dạng mutex hoặc như một biến có điều kiện.
Do đó, semaphore có thể được coi là một cấu trúc phức tạp hơn so với biến có điều kiện, trong khi cấu trúc thứ hai nhẹ và linh hoạt hơn.
Semaphores có thể được sử dụng để triển khai quyền truy cập độc quyền vào các biến, tuy nhiên chúng được sử dụng để đồng bộ hóa. Mặt khác, Mutexes có ngữ nghĩa liên quan chặt chẽ đến việc loại trừ lẫn nhau: chỉ quá trình đã khóa tài nguyên mới được phép mở khóa.
Rất tiếc, bạn không thể triển khai đồng bộ hóa với mutexes, đó là lý do tại sao chúng tôi có các biến điều kiện. Cũng lưu ý rằng với các biến điều kiện, bạn có thể mở khóa tất cả các chuỗi đang chờ trong cùng một thời điểm bằng cách sử dụng mở khóa phát sóng. Điều này không thể được thực hiện với semaphores.
semaphore và biến điều kiện rất giống nhau và được sử dụng hầu hết cho các mục đích giống nhau. Tuy nhiên, có những khác biệt nhỏ có thể làm cho một cái tốt hơn. Ví dụ, để thực hiện đồng bộ hóa rào cản, bạn sẽ không thể sử dụng semaphore, nhưng một biến điều kiện là lý tưởng.
Đồng bộ hóa rào cản là khi bạn muốn tất cả các luồng của mình đợi cho đến khi mọi người đã đến một phần nhất định trong chức năng luồng. điều này có thể được thực hiện bằng cách có một biến tĩnh mà ban đầu là giá trị của tổng số luồng giảm theo từng luồng khi nó đạt đến rào cản đó. điều này có nghĩa là chúng ta muốn mỗi luồng ở chế độ ngủ cho đến khi luồng cuối cùng đến. semaphore sẽ làm ngược lại! với một semaphore, mỗi luồng sẽ tiếp tục chạy và luồng cuối cùng (sẽ đặt giá trị semaphore thành 0) sẽ chuyển sang chế độ ngủ.
Mặt khác, một biến điều kiện là lý tưởng. khi mỗi luồng đi đến rào cản, chúng tôi kiểm tra xem bộ đếm tĩnh của chúng tôi có bằng không. nếu không, chúng tôi đặt luồng ở trạng thái ngủ với hàm chờ biến điều kiện. khi luồng cuối cùng đến hàng rào, giá trị bộ đếm sẽ giảm xuống 0 và luồng cuối cùng này sẽ gọi hàm tín hiệu biến điều kiện sẽ đánh thức tất cả các luồng khác!
Tôi gửi các biến điều kiện theo đồng bộ hóa màn hình. Tôi thường thấy semaphores và màn hình là hai kiểu đồng bộ hóa khác nhau. Có sự khác biệt giữa cả hai về lượng dữ liệu trạng thái vốn có và cách bạn muốn lập mô hình mã - nhưng thực sự không có bất kỳ vấn đề nào có thể được giải quyết bởi cái này mà không phải cái kia.
Tôi có xu hướng viết mã theo hình thức giám sát; trong hầu hết các ngôn ngữ tôi làm việc liên quan đến mutexes, biến điều kiện và một số biến trạng thái hỗ trợ. Nhưng semaphores cũng sẽ làm công việc.
Các mutex
và conditional variables
được kế thừa từ semaphore
.
mutex
, semaphore
sử dụng hai trạng thái: 0, 1condition variables
bộ semaphore
đếm sử dụng.Chúng giống như đường cú pháp