Tôi có phải lấy khóa trước khi gọi condition_variable.notify_one () không?


90

Tôi hơi bối rối về việc sử dụng std::condition_variable. Tôi hiểu rằng tôi phải tạo một unique_locktrên mutextrước khi gọi condition_variable.wait(). Những gì tôi không thể tìm thấy là liệu tôi cũng nên có được một khóa duy nhất trước khi gọi notify_one()hoặc notify_all().

Các ví dụ trên cppreference.com là xung đột. Ví dụ: trang Inform_one đưa ra ví dụ sau:

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>

std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying...\n";
    cv.notify_one();

    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done) {
        lk.unlock();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lk.lock();
        std::cerr << "Notifying again...\n";
        cv.notify_one();
    }
}

int main()
{
    std::thread t1(waits), t2(signals);
    t1.join(); t2.join();
}

Ở đây, khóa không được mua cho lần đầu tiên notify_one(), nhưng có được cho lần thứ hai notify_one(). Nhìn qua các trang khác với các ví dụ, tôi thấy những điều khác nhau, chủ yếu là không có được khóa.

  • Tôi có thể tự chọn khóa mutex trước khi gọi không notify_one()và tại sao tôi lại chọn khóa?
  • Trong ví dụ đã cho, tại sao không có khóa cho lần đầu tiên notify_one(), nhưng lại có cho các cuộc gọi tiếp theo. Ví dụ này có sai hay có một số cơ sở?

Câu trả lời:


77

Bạn không cần phải giữ khóa khi gọi condition_variable::notify_one(), nhưng nó không sai theo nghĩa là hành vi vẫn được xác định rõ và không phải là lỗi.

Tuy nhiên, nó có thể là một "bi quan" vì bất kỳ chuỗi chờ nào được thực hiện có thể chạy được (nếu có) sẽ ngay lập tức cố gắng lấy khóa mà chuỗi thông báo đang giữ. Tôi nghĩ rằng đó là một nguyên tắc nhỏ để tránh giữ khóa được liên kết với một biến điều kiện trong khi gọi notify_one()hoặc notify_all(). Xem Pthread Mutex: pthread_mutex_unlock () tiêu tốn nhiều thời gian cho một ví dụ trong đó giải phóng một khóa trước khi gọi pthread tương đương với notify_one()hiệu suất được cải thiện có thể đo lường được.

Hãy nhớ rằng lệnh lock()gọi trong whilevòng lặp là cần thiết tại một số thời điểm, vì khóa cần được giữ trong quá trình while (!done)kiểm tra điều kiện vòng lặp. Nhưng nó không cần phải được giữ cho cuộc gọi đến notify_one().


2016-02-27 : Cập nhật lớn để giải quyết một số câu hỏi trong nhận xét về việc liệu có điều kiện cuộc đua là khóa không giúp ích cho notify_one()cuộc gọi. Tôi biết bản cập nhật này là muộn vì câu hỏi đã được hỏi gần hai năm trước, nhưng tôi muốn giải quyết câu hỏi của @ Cookie về một điều kiện chủng tộc có thể xảy ra nếu nhà sản xuất ( signals()trong ví dụ này) gọi notify_one()ngay trước khi người tiêu dùng ( waits()trong ví dụ này) là có thể gọi điện wait().

Điều quan trọng là điều gì sẽ xảy ra i- đó là đối tượng thực sự cho biết liệu người tiêu dùng có "công việc" để làm hay không. Đây condition_variablechỉ là một cơ chế để cho phép người tiêu dùng chờ đợi sự thay đổi một cách hiệu quả i.

Nhà sản xuất cần giữ khóa khi cập nhật i, và người tiêu dùng phải giữ khóa khi kiểm tra ivà gọi điện condition_variable::wait()(nếu cần chờ đợi). Trong trường hợp này, chìa khóa phải giống với trường hợp giữ khóa (thường được gọi là phần quan trọng) khi người tiêu dùng thực hiện thao tác kiểm tra và chờ đợi này. Vì phần quan trọng được giữ khi nhà sản xuất cập nhật ivà khi người tiêu dùng kiểm tra và chờ đợi i, nên không có cơ hội iđể thay đổi giữa thời điểm người tiêu dùng kiểm tra ivà khi người tiêu dùng gọi condition_variable::wait(). Đây là điểm mấu chốt để sử dụng đúng các biến điều kiện.

Tiêu chuẩn C ++ nói rằng condition_variable :: wait () hoạt động như sau khi được gọi với một vị từ (như trong trường hợp này):

while (!pred())
    wait(lock);

Có hai tình huống có thể xảy ra khi người tiêu dùng kiểm tra i:

  • nếu ilà 0 thì người tiêu dùng gọi cv.wait(), sau đó isẽ vẫn là 0 khi wait(lock)phần thực hiện được gọi - việc sử dụng đúng các khóa đảm bảo điều đó. Trong trường hợp này, nhà sản xuất không có cơ hội gọi đến vòng lặp condition_variable::notify_one()của nó whilecho đến khi người tiêu dùng đã gọi cv.wait(lk, []{return i == 1;})(và wait()cuộc gọi đã thực hiện mọi thứ cần làm để 'bắt' đúng một thông báo - wait()sẽ không mở khóa cho đến khi nó thực hiện điều đó ). Vì vậy, trong trường hợp này, người tiêu dùng không thể bỏ lỡ thông báo.

  • nếu iđã là 1 khi người tiêu dùng gọi cv.wait(), wait(lock)phần triển khai sẽ không bao giờ được gọi vì while (!pred())thử nghiệm sẽ khiến vòng lặp nội bộ kết thúc. Trong tình huống này, không có vấn đề gì khi cuộc gọi đến message_one () xảy ra - người tiêu dùng sẽ không chặn.

Ví dụ ở đây có thêm sự phức tạp của việc sử dụng donebiến để báo hiệu trở lại chuỗi nhà sản xuất mà người tiêu dùng đã nhận ra i == 1, nhưng tôi không nghĩ điều này thay đổi phân tích chút nào vì tất cả quyền truy cập done(cho cả đọc và sửa đổi ) được thực hiện trong cùng các phần quan trọng liên quan đến icondition_variable.

Nếu bạn nhìn vào câu hỏi mà @ eh9 đã chỉ đến, Đồng bộ hóa không đáng tin cậy bằng cách sử dụng std :: atom và std :: condition_variable , bạn sẽ thấy điều kiện chủng tộc. Tuy nhiên, mã được đăng trong câu hỏi đó vi phạm một trong những quy tắc cơ bản của việc sử dụng biến điều kiện: Nó không giữ một phần quan trọng khi thực hiện kiểm tra và chờ đợi.

Trong ví dụ đó, mã trông giống như:

if (--f->counter == 0)      // (1)
    // we have zeroed this fence's counter, wake up everyone that waits
    f->resume.notify_all(); // (2)
else
{
    unique_lock<mutex> lock(f->resume_mutex);
    f->resume.wait(lock);   // (3)
}

Bạn sẽ nhận thấy rằng wait()ở # 3 được thực hiện trong khi giữ f->resume_mutex. Nhưng việc kiểm tra xem có wait()cần thiết hay không ở bước số 1 sẽ không được thực hiện khi đang giữ khóa đó (ít liên tục hơn nhiều đối với việc kiểm tra và chờ đợi), đây là một yêu cầu để sử dụng đúng các biến điều kiện). Tôi tin rằng người gặp vấn đề với đoạn mã đó nghĩ rằng vì f->counterlà một std::atomicloại nên nó sẽ đáp ứng yêu cầu. Tuy nhiên, tính nguyên tử được cung cấp bởi std::atomickhông kéo dài đến lần gọi tiếp theo tới f->resume.wait(lock). Trong ví dụ này, có một cuộc đua giữa thời điểm f->counterđược chọn (bước # 1) và khi nào wait()được gọi (bước # 3).

Chủng tộc đó không tồn tại trong ví dụ của câu hỏi này.


2
nó có ý nghĩa sâu sắc hơn: domaigne.com/blog/computing/… Đáng chú ý, vấn đề pthread mà bạn đề cập phải được giải quyết bằng phiên bản mới hơn hoặc phiên bản được xây dựng với các cờ chính xác. (để kích hoạt wait morphingtố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.
v.oddou

6
@Michael: Theo hiểu biết của tôi thì cuối cùng người tiêu dùng cần gọi điện the_condition_variable.wait(lock);. Nếu không cần khóa để đồng bộ hóa nhà sản xuất và người tiêu dùng (giả sử bên dưới là hàng đợi spsc không khóa), thì khóa đó sẽ không có mục đích gì nếu nhà sản xuất không khóa nó. Tốt bởi tôi. Nhưng không có rủi ro cho một cuộc đua hiếm hoi? Nếu nhà sản xuất không giữ khóa, anh ta không thể gọi thông báo_one trong khi người tiêu dùng ở ngay trước khi chờ đợi? Sau đó, người tiêu dùng chạm chờ đợi và sẽ không thức dậy ...
Cookie

1
Ví dụ: trong đoạn mã trên, người tiêu dùng đang ở std::cout << "Waiting... \n";trong khi nhà sản xuất làm cv.notify_one();, sau đó chuông báo thức bị mất tích ... Hay tôi đang thiếu thứ gì đó ở đây?
Cookie,

1
@Bánh quy. Có, có một điều kiện cuộc đua ở đó. Xem stackoverflow.com/questions/20982270/…
eh9

1
@ eh9: Chết tiệt, thỉnh thoảng tôi mới tìm ra nguyên nhân gây ra lỗi đóng băng mã của mình nhờ vào bình luận của bạn. Đó là do trường hợp chính xác của điều kiện chủng tộc. Mở khóa mutex sau khi thông báo đã giải quyết hoàn toàn sự cố ... Cảm ơn rất nhiều!
galinette

10

Tình hình

Sử dụng vc10 và Boost 1.56, tôi đã triển khai một hàng đợi đồng thời khá giống như bài đăng trên blog này đề xuất. Tác giả mở khóa mutex để giảm thiểu sự tranh chấp, tức là, notify_one()được gọi với mutex được mở khóa:

void push(const T& item)
{
  std::unique_lock<std::mutex> mlock(mutex_);
  queue_.push(item);
  mlock.unlock();     // unlock before notificiation to minimize mutex contention
  cond_.notify_one(); // notify one waiting thread
}

Việc mở khóa mutex được hỗ trợ bởi một ví dụ trong tài liệu Boost :

void prepare_data_for_processing()
{
    retrieve_data();
    prepare_data();
    {
        boost::lock_guard<boost::mutex> lock(mut);
        data_ready=true;
    }
    cond.notify_one();
}

Vấn đề

Tuy nhiên, điều này dẫn đến hành vi thất thường sau:

  • trong khi notify_one()vẫn chưa được gọi vẫn cond_.wait()có thể bị gián đoạn quaboost::thread::interrupt()
  • một lần notify_one()được gọi lần đầu tiên cond_.wait()bế tắc; sự chờ đợi không thể kết thúc bằng boost::thread::interrupt()hoặc boost::condition_variable::notify_*()nữa.

Giải pháp

Loại bỏ dòng mlock.unlock()làm cho mã hoạt động như mong đợi (thông báo và ngắt kết thúc chờ đợi). Lưu ý rằng notify_one()được gọi với mutex vẫn bị khóa, nó sẽ được mở khóa ngay sau đó khi rời khỏi phạm vi:

void push(const T& item)
{
  std::lock_guard<std::mutex> mlock(mutex_);
  queue_.push(item);
  cond_.notify_one(); // notify one waiting thread
}

Điều đó có nghĩa là ít nhất với việc triển khai luồng cụ thể của tôi, mutex không được mở khóa trước khi gọi boost::condition_variable::notify_one(), mặc dù cả hai cách đều có vẻ đúng.


Bạn đã báo cáo sự cố này cho Boost.Thread chưa? Tôi không thể tìm thấy công việc tương tự có svn.boost.org/trac/boost/...
magras

@magras Đáng tiếc là tôi không làm vậy, không hiểu sao tôi không xem xét điều này. Và rất tiếc, tôi không thành công trong việc tạo lại lỗi này bằng cách sử dụng hàng đợi đã đề cập.
Matthäus Brandl

Tôi không chắc mình thấy việc dậy sớm có thể gây ra bế tắc như thế nào. Cụ thể, nếu bạn thoát ra khỏi cond_.wait () trong pop () sau khi push () giải phóng hàng đợi mutex nhưng trước khi message_one () được gọi - Pop () sẽ thấy hàng đợi không trống và sử dụng mục nhập mới hơn là đang chờ đợi. nếu bạn thoát ra khỏi cond_.wait () trong khi push () đang cập nhật hàng đợi, khóa sẽ được giữ bằng push (), do đó pop () sẽ chặn chờ khóa được giải phóng. Bất kỳ lần thức dậy sớm nào khác sẽ giữ khóa, không cho push () sửa đổi hàng đợi trước khi pop () gọi lần chờ tiếp theo (). Tôi đã bỏ lở những gì?
Kevin

4

Như những người khác đã chỉ ra, bạn không cần phải giữ khóa khi gọi notify_one(), về điều kiện chủng tộc và các vấn đề liên quan đến luồng. Tuy nhiên, trong một số trường hợp, bạn có thể phải giữ khóa để ngăn khóa condition_variablebị phá hủy trước khi notify_one()được gọi. Hãy xem xét ví dụ sau:

thread t;

void foo() {
    std::mutex m;
    std::condition_variable cv;
    bool done = false;

    t = std::thread([&]() {
        {
            std::lock_guard<std::mutex> l(m);  // (1)
            done = true;  // (2)
        }  // (3)
        cv.notify_one();  // (4)
    });  // (5)

    std::unique_lock<std::mutex> lock(m);  // (6)
    cv.wait(lock, [&done]() { return done; });  // (7)
}

void main() {
    foo();  // (8)
    t.join();  // (9)
}

Giả sử có một chuyển đổi ngữ cảnh sang luồng mới được tạo tsau khi chúng tôi tạo nó nhưng trước khi chúng tôi bắt đầu chờ biến điều kiện (ở đâu đó giữa (5) và (6)). Luồng tcó được khóa (1), đặt biến vị từ (2) và sau đó giải phóng khóa (3). Giả sử có một chuyển đổi ngữ cảnh khác ngay tại thời điểm này trước khi notify_one()(4) được thực thi. Luồng chính nhận được khóa (6) và thực hiện dòng (7), tại thời điểm đó vị từ trả về truevà không có lý do gì để chờ đợi, vì vậy nó giải phóng khóa và tiếp tục. footrả về (8) và các biến trong phạm vi của nó (bao gồm cả cv) bị hủy. Trước khi luồng tcó thể tham gia luồng chính (9), nó phải kết thúc quá trình thực thi, vì vậy nó sẽ tiếp tục từ vị trí đã dừng để thực thicv.notify_one()(4), tại thời điểm đó cvđã bị phá hủy!

Cách khắc phục khả thi trong trường hợp này là tiếp tục giữ khóa khi gọi notify_one(tức là loại bỏ phạm vi kết thúc bằng dòng (3)). Bằng cách làm như vậy, chúng tôi đảm bảo rằng tcác lệnh gọi luồng notify_onetrước đó cv.waitcó thể kiểm tra biến vị ngữ mới được thiết lập và tiếp tục, vì nó sẽ cần có được khóa, t hiện đang được giữ, để thực hiện kiểm tra. Vì vậy, chúng tôi đảm bảo rằng cvkhông được truy cập bởi luồng tsau khi footrả về.

Tóm lại, vấn đề trong trường hợp cụ thể này không thực sự là về luồng, mà là về vòng đời của các biến được thu thập bằng tham chiếu. cvđược nắm bắt bằng cách tham chiếu thông qua luồng t, do đó bạn phải đảm bảo cvvẫn tồn tại trong suốt thời gian thực thi của luồng. Các ví dụ khác được trình bày ở đây không bị vấn đề này, bởi vì condition_variablemutexcác đối tượng được xác định trong phạm vi toàn cục, do đó chúng được đảm bảo duy trì tồn tại cho đến khi chương trình thoát.


1

@Michael Burr là chính xác. condition_variable::notify_onekhông yêu cầu khóa biến. Tuy nhiên, không có gì ngăn cản bạn sử dụng khóa trong trường hợp đó, như ví dụ minh họa.

Trong ví dụ đã cho, khóa được thúc đẩy bởi việc sử dụng đồng thời biến i. Vì signalsluồng sửa đổi biến, nó cần đảm bảo rằng không có luồng nào khác truy cập vào nó trong thời gian đó.

Khóa được sử dụng cho bất kỳ tình huống nào yêu cầu đồng bộ hóa , tôi không nghĩ rằng chúng ta có thể nêu nó một cách tổng quát hơn.


tất nhiên, nhưng trên hết, chúng cũng cần được sử dụng trong cunjunction với các biến điều kiện để toàn bộ mẫu thực sự hoạt động. Đáng chú ý là waithàm biến điều kiện đang giải phóng khóa bên trong cuộc gọi và chỉ trả về sau khi nó đã yêu cầu lại khóa. sau đó, bạn có thể an toàn kiểm tra tình trạng của mình vì bạn đã có được "quyền đọc". nếu nó vẫn không phải là những gì bạn đang chờ đợi, bạn quay trở lại wait. đây là mô hình. btw, ví dụ này KHÔNG tôn trọng nó.
v.oddou

1

Trong một số trường hợp, khi cv có thể bị chiếm (khóa) bởi các luồng khác. Bạn cần phải khóa và giải phóng nó trước khi thông báo cho _ * ().
Nếu không, thông báo _ * () có thể không được thực thi.


1

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 () để 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) // Reached -1000 ?
    cv.notify_one();
}

bool start()
{
  if (count.fetch_add(1) >= 0)
    return true;
  // Failure.
  stop();
  return false;
}

void cancel()
{
  if (count.fetch_sub(1000) == 0)  // Reached -1000?
    return;
  // Wait till count reached -1000.
  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())
{
  // Do stuff
  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 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) // Reached -1000 ?
  {
    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 đó.

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.