Chỉ thêm câu trả lời này vì tôi nghĩ câu trả lời được chấp nhận có thể gây hiểu lầm. Trong mọi trường hợp, bạn sẽ cần phải khóa mutex, trước khi gọi thông báo_one () ở đâu đó để mã của bạn được an toàn theo chuỗi, mặc dù bạn có thể mở khóa lại trước khi thực sự gọi thông báo _ * ().
Để làm rõ, bạn PHẢI mở khóa trước khi vào wait (lk) vì wait () mở khóa lk và nó sẽ là Hành vi không xác định nếu khóa không được khóa. Đây không phải là trường hợp của Inform_one (), nhưng bạn cần đảm bảo rằng bạn sẽ không gọi thông báo _ * () trước khi nhập wait () và để cuộc gọi đó mở khóa mutex; điều này rõ ràng chỉ có thể được thực hiện bằng cách khóa cùng một mutex đó trước khi bạn gọi thông báo _ * ().
Ví dụ, hãy xem xét trường hợp sau:
std::atomic_int count;
std::mutex cancel_mutex;
std::condition_variable cancel_cv;
void stop()
{
if (count.fetch_sub(1) == -999)
cv.notify_one();
}
bool start()
{
if (count.fetch_add(1) >= 0)
return true;
stop();
return false;
}
void cancel()
{
if (count.fetch_sub(1000) == 0)
return;
std::unique_lock<std::mutex> lk(cancel_mutex);
cancel_cv.wait(lk);
}
Cảnh báo : mã này có một lỗi.
Ý tưởng là như sau: các luồng gọi start () và stop () theo cặp, nhưng chỉ khi start () trả về true. Ví dụ:
if (start())
{
stop();
}
Một (khác) luồng tại một số thời điểm sẽ gọi hủy () và sau khi quay lại từ hủy () sẽ hủy các đối tượng cần thiết tại 'Làm công cụ'. Tuy nhiên, hủy () được cho là không trả về trong khi có các chuỗi giữa start () và stop (), và sau khi hủy () được thực thi dòng đầu tiên của nó, start () sẽ luôn trả về false, vì vậy không có chuỗi mới nào sẽ nhập vào 'Do khu vực thứ.
Hoạt động đúng không?
Lý do là như sau:
1) Nếu bất kỳ luồng nào thực thi thành công dòng đầu tiên của start () (và do đó sẽ trả về true) thì không có luồng nào thực hiện dòng đầu tiên của hàm hủy () (chúng tôi giả định rằng tổng số luồng nhỏ hơn nhiều hơn 1000 bởi đường).
2) Ngoài ra, trong khi một luồng thực thi thành công dòng đầu tiên của start (), nhưng chưa phải là dòng đầu tiên của stop () thì không thể có luồng nào thực hiện thành công dòng đầu tiên của hàm hủy () (lưu ý rằng chỉ một luồng bao giờ gọi hủy ()): giá trị được trả về bởi fetch_sub (1000) sẽ lớn hơn 0.
3) Sau khi một luồng thực thi dòng đầu tiên của hàm hủy (), dòng đầu tiên của start () sẽ luôn trả về giá trị false và một chuỗi gọi start () sẽ không vào vùng 'Thực hiện công việc' nữa.
4) Số lượng lệnh gọi bắt đầu () và dừng () luôn cân bằng, vì vậy sau khi dòng đầu tiên của lệnh hủy () được thực hiện không thành công, sẽ luôn có một thời điểm mà lệnh gọi (lệnh cuối cùng) dừng () gây đếm đạt đến -1000 và do đó, message_one () sẽ được gọi. Lưu ý rằng điều đó chỉ có thể xảy ra khi dòng hủy đầu tiên dẫn đến luồng đó bị lọt qua.
Ngoài vấn đề chết đói trong đó quá nhiều chuỗi đang gọi start () / stop () mà số lượng không bao giờ đạt đến -1000 và hủy () không bao giờ trả về, một lỗi có thể chấp nhận là "không thể xảy ra và không bao giờ tồn tại lâu", có một lỗi khác:
Có thể có một luồng bên trong vùng 'Do công việc', giả sử nó chỉ đang gọi stop (); tại thời điểm đó, một luồng thực thi dòng đầu tiên của hàm hủy () đọc giá trị 1 với fetch_sub (1000) và giảm xuống. Nhưng trước khi cần đến mutex và / hoặc thực hiện cuộc gọi đợi (lk), luồng đầu tiên thực hiện dòng đầu tiên của stop (), đọc -999 và gọi cv.notify_one ()!
Sau đó, lệnh gọi tới Inform_one () này được thực hiện TRƯỚC KHI chúng ta đợi () - nhập vào biến điều kiện! Và chương trình sẽ bị khóa vô thời hạn.
Vì lý do này, chúng tôi sẽ không thể gọi thông báo_one () cho đến khi chúng tôi gọi là wait (). Lưu ý rằng sức mạnh của một biến điều kiện nằm ở chỗ nó có thể mở khóa nguyên tử mutex, kiểm tra xem một lệnh gọi tới notification_one () đã xảy ra và chuyển sang chế độ ngủ hay chưa. Bạn không thể đánh lừa nó, nhưng bạn làm cần thiết để giữ mutex khóa bất cứ khi nào bạn thay đổi biến mà có thể thay đổi tình trạng này từ false thành true và giữ nó bị khóa trong khi gọi notify_one () vì điều kiện chủng tộc như mô tả ở đây.
Tuy nhiên, trong ví dụ này không có điều kiện. Tại sao tôi không sử dụng làm điều kiện 'count == -1000'? Bởi vì điều đó không thú vị chút nào ở đây: ngay sau khi đạt đến -1000, chúng tôi chắc chắn rằng sẽ không có luồng mới nào lọt vào khu vực 'Thực hiện công việc'. Hơn nữa, các luồng vẫn có thể gọi start () và sẽ tăng số lượng (đến -999 và -998, v.v.) nhưng chúng tôi không quan tâm đến điều đó. Điều quan trọng duy nhất là -1000 đã đạt - để chúng tôi biết chắc chắn rằng không có chủ đề nào nữa trong khu vực 'Thực hiện công việc'. Chúng tôi chắc chắn rằng đây là trường hợp khi Inform_one () đang được gọi, nhưng làm thế nào để đảm bảo rằng chúng tôi không gọi Inform_one () trước khi hủy () khóa mutex của nó? Tất nhiên, chỉ cần khóa hủy bỏ_mutex trước thông báo_one () sẽ không hữu ích.
Vấn đề là, mặc dù chúng tôi không chờ đợi một điều kiện, nhưng vẫn có một điều kiện và chúng tôi cần khóa mutex
1) trước khi điều kiện đó đạt được 2) trước khi chúng tôi gọi thông báo_one.
Do đó, mã chính xác trở thành:
void stop()
{
if (count.fetch_sub(1) == -999)
{
cancel_mutex.lock();
cancel_mutex.unlock();
cv.notify_one();
}
}
[... cùng bắt đầu () ...]
void cancel()
{
std::unique_lock<std::mutex> lk(cancel_mutex);
if (count.fetch_sub(1000) == 0)
return;
cancel_cv.wait(lk);
}
Tất nhiên đây chỉ là một ví dụ nhưng các trường hợp khác rất giống nhau; trong hầu hết các trường hợp khi bạn sử dụng một biến có điều kiện, bạn sẽ cần phải khóa mutex đó (trong thời gian ngắn) trước khi gọi thông báo_one (), hoặc nếu không, bạn có thể gọi nó trước khi gọi hàm wait ().
Lưu ý rằng tôi đã mở khóa mutex trước khi gọi thông báo_one () trong trường hợp này, bởi vì nếu không thì có khả năng (nhỏ) là lệnh gọi thông báo_một () đánh thức chuỗi đang chờ biến điều kiện mà sau đó sẽ cố gắng lấy mutex và trước khi chúng tôi phát hành lại mutex. Điều đó chỉ hơi chậm hơn mức cần thiết.
Ví dụ này khá đặc biệt ở chỗ dòng thay đổi điều kiện được thực thi bởi cùng một luồng gọi là wait ().
Thông thường hơn là trường hợp một luồng chỉ cần đợi một điều kiện trở thành true và một luồng khác lấy khóa trước khi thay đổi các biến liên quan đến điều kiện đó (khiến nó có thể trở thành true). Trong trường hợp đó mutex được khóa ngay trước (và sau) tình trạng này đã trở thành sự thật - vì vậy nó là hoàn toàn ok để chỉ cần mở khóa mutex trước khi gọi thông báo _ * () trong trường hợp đó.
wait morphing
tối ưu hóa) Quy tắc ngón tay cái được giải thích trong liên kết này: thông báo với khóa CÓ tốt hơn trong các tình huống có nhiều hơn 2 chủ đề để có kết quả dễ đoán hơn.