我目前正在学习 C++ 中的多线程。我对条件变量有疑问。 如果我有这样的代码:
std::condition_variable cvS;
std::condition_variable cvR;
std::condition_variable cv;
std::mutex gMtx;
int countm = 0;
void SenderS()
{
std::unique_lock<std::mutex> lck(gMtx);
while(countm >= 5){
std::cout << std::this_thread::get_id() <<"exceedin S" << std::endl;
cv.wait(lck); //or cvS.wait(lck);
}
countm++;
std::cout<< std::this_thread::get_id() << "S"<< countm << std::endl;
lck.unlock();
cv.notify_one(); //or cvR.notify_one();
}
void ReceiverS()
{
std::unique_lock<std::mutex> lck(gMtx);
while(countm <= 0){
std::cout << std::this_thread::get_id() <<"exceedin R" << std::endl;
cv.wait(lck); //or cvR.wait(lck);
}
countm--;
std::cout << std::this_thread::get_id() <<"R" << countm << std::endl;
lck.unlock();
cv.notify_one(); //or cvS.notify_one();
}
对于这种情况,使用一个或两个条件变量有什么区别吗?一般来说,对于生产者-消费者模型,我应该使用一个还是两个条件变量?
此外,
cvR.notify_one()
只会通知执行cvR.wait()
的线程吗?
根据我个人的分析,如果使用单个条件变量,应该使用
notify_all()
来唤醒所有等待的线程,以避免唤醒错误的线程。如果使用两个条件变量,用notify_one()
唤醒“另一边”的一个线程应该没问题。
我不知道这是不是一个正确的规则。
这是我对这个问题的回答,我觉得这里也适用。我认为您需要两个条件变量或一个原子标志。
这是使用互斥锁和条件变量的规范乒乓球。请注意,1) 您需要两个条件变量来使乒乓球工作,并且 2) 您必须小心地将输出语句放在仍然持有锁的块中。你的代码很接近。
#include <iostream>
#include <condition_variable>
#include <atomic>
#include <thread>
class PingPong {
public:
PingPong() {
t0_ = std::thread(&PingPong::ping, this);
t1_ = std::thread(&PingPong::pong, this);
}
~PingPong() {
if (t0_.joinable())
t0_.join();
if (t1_.joinable())
t1_.join();
}
void ping() {
while(counter <= 20) {
{
std::unique_lock<std::mutex> lck(mutex_);
cv0_.wait(lck, [this]{ return ready_ == false; });
ready_ = true;
std::cout << "ping counter: " << counter << std::endl;
}
++counter;
cv1_.notify_one();
}
}
void pong() {
while(counter < 20) {
{
std::unique_lock<std::mutex> lck(mutex_);
cv1_.wait(lck, [this]{ return ready_ == true; });
ready_ = false;
std::cout << "pong counter: " << counter << std::endl;
}
cv0_.notify_one();
}
}
private:
bool ready_{false};
std::mutex mutex_;
std::condition_variable cv0_, cv1_;
std::atomic<int> counter{};
std::thread t0_, t1_;
};
int main(){
PingPong p{};
}
这将导致以下输出。
ping counter: 0
pong counter: 1
ping counter: 1
pong counter: 2
ping counter: 2
pong counter: 3
ping counter: 3
pong counter: 4
ping counter: 4
pong counter: 5
ping counter: 5
pong counter: 6
ping counter: 6
pong counter: 7
ping counter: 7
pong counter: 8
ping counter: 8
pong counter: 9
ping counter: 9
...
根据您的平台,使用原子标志而不是条件变量可能性能更高(并且更容易理解)。这会产生与上面相同的输出。
class PingPongAtomicFlag {
public:
PingPongAtomicFlag() {
t0_ = std::thread([this]() { ping(); });
t1_ = std::thread([this]() { pong(); });
}
~PingPongAtomicFlag() {
if (t0_.joinable())
t0_.join();
if (t1_.joinable())
t1_.join();
}
void ping() {
while(counter_ <= 20) {
potato_.wait(true);
std::cout << "ping counter: " << counter_ << std::endl;
potato_.test_and_set();
++counter_;
potato_.notify_one();
}
}
void pong() {
while(counter_ < 20) {
potato_.wait(false);
std::cout << "pong counter: " << counter_ << std::endl;
potato_.clear();
potato_.notify_one();
}
}
private:
std::atomic_flag potato_;
std::atomic<int> counter_{};
std::thread t0_, t1_;
};