在生产者-消费者问题中应该使用多少个条件变量?

问题描述 投票:0回答:2

我目前正在学习 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()
的线程吗?

c++ multithreading producer-consumer conditional-variable
2个回答
0
投票

根据我个人的分析,如果使用单个条件变量,应该使用

notify_all()
来唤醒所有等待的线程,以避免唤醒错误的线程。如果使用两个条件变量,用
notify_one() 
唤醒“另一边”的一个线程应该没问题。 我不知道这是不是一个正确的规则。


0
投票

这是我对这个问题的回答,我觉得这里也适用。我认为您需要两个条件变量或一个原子标志。

带有互斥量和两个条件变量的 Ping-Pong

这是使用互斥锁和条件变量的规范乒乓球。请注意,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_;
};
© www.soinside.com 2019 - 2024. All rights reserved.