我正在尝试学习C ++ 11中的并发编程。我试图为经典的生产者消费者并发问题编写代码。请问您对此有何评论?

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

using std::deque;
std::mutex mu,cout_mu;
std::condition_variable cond;

class Buffer
{
public:
    void add(int num) {
        while (true) {
            std::unique_lock<std::mutex> locker(mu);
            cond.wait(locker, [this](){return buffer_.size() < size_;});
            buffer_.push_back(num);
            locker.unlock();
            cond.notify_all();
            return;
        }
    }
    int remove() {
        while (true)
        {
            std::unique_lock<std::mutex> locker(mu);
            cond.wait(locker, [this](){return buffer_.size() > 0;});
            int back = buffer_.back();
            buffer_.pop_back(); 
            locker.unlock();
            cond.notify_all();
            return back;
        }
    }
    Buffer() {}
private:
    deque<int> buffer_;
    const unsigned int size_ = 10;
};

class Producer
{
public:
    Producer(Buffer* buffer)
    {
        this->buffer_ = buffer;
    }
    void run() {
        while (true) {
            int num = std::rand() % 100;
            buffer_->add(num);
            cout_mu.lock();
            std::cout << "Produced: " << num << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
            cout_mu.unlock();
        }
    }
private:
    Buffer *buffer_;
};

class Consumer
{
public:
    Consumer(Buffer* buffer)
    {
        this->buffer_ = buffer;
    }
    void run() {
        while (true) {
            int num = buffer_->remove();
            cout_mu.lock();
            std::cout << "Consumed: " << num << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
            cout_mu.unlock();
        }
    }
private:
    Buffer *buffer_;
};

int main() {
    Buffer b;
    Producer p(&b);
    Consumer c(&b);

    std::thread producer_thread(&Producer::run, &p);
    std::thread consumer_thread(&Consumer::run, &c);

    producer_thread.join();
    consumer_thread.join();
    getchar();
    return 0;
}


评论

为了与其他方法保持一致,是将Buffer :: add的主体包裹在while中,还是在其背后有某些原因?似乎它没有用于控制流,也没有引入有意义的作用域。我只是想知道我是否缺少一些窍门。

没什么特别的。它只是说明了生产是无限的。但是,如果buffer_已满,则该线程将使其自身进入等待或挂起状态(生产停止,直到消耗掉某些东西),而不会浪费CPU周期。

好吧,我只是感到困惑,因为使用while(true)和无条件的返回除了引入作用域(用括号括起来会更好)之外,没有任何意义。仍然不确定我是否理解了这一点(除了一致性?),但这似乎不是技术性的,所以我的好奇心已经得到满足。
是在Buffer :: remove中需要的时间吗?另外,如果Producer和Consumer运行方法中没有std :: cout,则cout_mu锁将是不必要的,对吗?

这种模式的扩展适用于从多个生产者那里消费的消费者,可以在这里找到codereview.stackexchange.com/questions/235651/…

#1 楼

优先于指针而不是指针

由于生产者和消费者必须有一个缓冲区,所以应该按引用(而不是指针)传递它。这也可以确保不会混淆缓冲区的所有权(指针的所有者负责删除它)。通过使用RAW指针,您不能告诉所有者,但是通过使用引用,您可以明确声明您没有在传递所有权。



当您使用this->时,这意味着您的变量存在范围界定问题(这是不良设计的代码味道)。请使用准确的变量名称,以免混淆变量的所属位置。

全局变量上的成员变量。 br />
class Producer
{
    Buffer&  buffer_;
public:
    Producer(Buffer& buffer)
        : buffer_(buffer)
    {}


因为它们是全局的,所以对所有缓冲区对象使用单个互斥锁/条件。这看起来像是设计缺陷。似乎互斥锁/条件属于该类,因此您只需锁定要操作的缓冲区(以便可以在同一应用程序中使用多个缓冲区)。
std::mutex mu,cout_mu;


@Morwenn已建议使用范围锁来确保互斥锁已正确锁定和解锁。但是我也会把睡眠移到锁的外面。这将导致当前线程停止运行,但其他线程无法继续,因为该锁在休眠状态时仍保持锁定。
<43优先于\ 4312079q br />

        cout_mu.lock();
        std::cout << "Produced: " << num << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
        cout_mu.unlock();


两者之间的区别是同花顺。通常,您不想手动冲洗(流中内置了良好的冲洗技术)。因此,通常最好在适当的时候让流自己冲洗。


回答评论:

        {
            std::unique_lock<std::mutex> locker(cout_mu);
            std::cout << "Produced: " << num << std::endl;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(50));


评论


\ $ \ begingroup \ $
非常感谢您的评论。优先使用指针而不是指针我对使用指针之上的引用感到困惑。我的意思是,无论是通过引用还是指针传递,生产者对象和消费者对象都没有分配给它们的缓冲区副本。最好避免这种情况,我只是在学习lambda表示法,在lambda函数内部捕获buffer_的正确方法是什么?如果我输入'[&buffer _](){return buffer_.size() \ $ \ endgroup \ $
– Robomatt
15年3月15日在12:19



\ $ \ begingroup \ $
__全局变量上的成员变量__您如何建议我使用成员互斥锁?
\ $ \ endgroup \ $
– Robomatt
2015年3月15日12:31



\ $ \ begingroup \ $
__全局变量上的成员变量__您如何建议我使用成员互斥锁?查看最新答案。
\ $ \ endgroup \ $
–马丁·约克
15年3月15日在19:26

\ $ \ begingroup \ $
可以解释一下此程序中while循环的用法吗?大小(添加,删除)。 ?
\ $ \ endgroup \ $
– Rupesh Yadav。
16年11月15日在5:31

\ $ \ begingroup \ $
@LokiAstari yaa无需争论,被弄糊涂了,所以被问到了,谢谢您的答复。
\ $ \ endgroup \ $
– Rupesh Yadav。
16年11月15日在15:17

#2 楼

我对您的代码有几点评论:




使用std::lock_guard非常适合处理互斥锁,因为它在离开示波器时会自动解锁获取的互斥锁。那是一个真正的好工具。您应该真正在所有可能的地方使用它。持续使用它可以确保您不会忘记解锁任何互斥锁。而且,即使在引发异常时,它也可以确保互斥锁被解锁。

许多平台都不愿意使其成为线程安全的,因为锁定和解锁互斥锁是有代价的。因此,应该改用std::rand的新伪随机数生成器,以确保可以一次使用多个生成器:比<random>更强大,更灵活,并且您可以std::rand对象使伪随机数生成线程安全,而不会浪费锁定/解锁互斥锁所需的时间,从而保持线程安全。
可以使用构造器初始化列表来构造对象,而不必分配给构造器主体中的成员变量:

while (true) {
    int num = buffer_->remove();
    std::lock_guard<std::mutex> lock(cout_mu);
    std::cout << "Consumed: " << num << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(50));
}