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

c90pui9n  于 2023-05-08  发布在  其他
关注(0)|答案(2)|浏览(131)

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

jvlzgdj9

jvlzgdj91#

这是我对这个question给出的答案,我认为在这里也适用。我认为你需要两个条件变量或一个原子标志。

带互斥和两个条件变量的乒乓

这是使用互斥体和条件变量的规范乒乓。注意1)你需要两个条件变量来使ping-pong工作,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_;
};
jhdbpxl9

jhdbpxl92#

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

相关问题