#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;
}
#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()
– 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));
}
评论
为了与其他方法保持一致,是将Buffer :: add的主体包裹在while中,还是在其背后有某些原因?似乎它没有用于控制流,也没有引入有意义的作用域。我只是想知道我是否缺少一些窍门。没什么特别的。它只是说明了生产是无限的。但是,如果buffer_已满,则该线程将使其自身进入等待或挂起状态(生产停止,直到消耗掉某些东西),而不会浪费CPU周期。
好吧,我只是感到困惑,因为使用while(true)和无条件的返回除了引入作用域(用括号括起来会更好)之外,没有任何意义。仍然不确定我是否理解了这一点(除了一致性?),但这似乎不是技术性的,所以我的好奇心已经得到满足。
是在Buffer :: remove中需要的时间吗?另外,如果Producer和Consumer运行方法中没有std :: cout,则cout_mu锁将是不必要的,对吗?
这种模式的扩展适用于从多个生产者那里消费的消费者,可以在这里找到codereview.stackexchange.com/questions/235651/…