我试图实现多线程作业,生产者和消费者,基本上我想要做的是,当消费者完成数据时,它会通知生产者,以便生产者提供新数据。 / p>
棘手的部分是,在我目前的impl中,制作人和消费者都互相通知并互相等待,我不知道如何正确地实现这部分。
例如,请参阅下面的代码,
mutex m;
condition_variable cv;
vector<int> Q; // this is the queue the consumer will consume
vector<int> Q_buf; // this is a buffer Q into which producer will fill new data directly
// consumer
void consume() {
while (1) {
if (Q.size() == 0) { // when consumer finishes data
unique_lock<mutex> lk(m);
// how to notify producer to fill up the Q?
...
cv.wait(lk);
}
// for-loop to process the elems in Q
...
}
}
// producer
void produce() {
while (1) {
// for-loop to fill up Q_buf
...
// once Q_buf is fully filled, wait until consumer asks to give it a full Q
unique_lock<mutex> lk(m);
cv.wait(lk);
Q.swap(Q_buf); // replace the empty Q with the full Q_buf
cv.notify_one();
}
}
我不确定使用mutex
和condition_variable
的上述代码是实现我的想法的正确方法,
请给我一些建议!
答案 0 :(得分:9)
代码错误地假设vector<int>::size()
和vector<int>::swap()
是原子的。他们不是。
此外,spurious wakeups必须由while
循环(或其他cv::wait
重载)处理。
修正:
mutex m;
condition_variable cv;
vector<int> Q;
// consumer
void consume() {
while(1) {
// Get the new elements.
vector<int> new_elements;
{
unique_lock<mutex> lk(m);
while(Q.empty())
cv.wait(lk);
new_elements.swap(Q);
}
// for-loop to process the elems in new_elements
}
}
// producer
void produce() {
while(1) {
vector<int> new_elements;
// for-loop to fill up new_elements
// publish new_elements
{
unique_lock<mutex> lk(m);
Q.insert(Q.end(), new_elements.begin(), new_elements.end());
cv.notify_one();
}
}
}
答案 1 :(得分:4)
也许这接近你想要的东西。我使用了2个条件变量来相互通知生产者和消费者,并引入了表示现在转向的变量:
#include <ctime>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
template<typename T>
class ReaderWriter {
private:
std::vector<std::thread> readers;
std::vector<std::thread> writers;
std::condition_variable readerCv, writerCv;
std::queue<T> data;
std::mutex readerMutex, writerMutex;
size_t noReaders, noWriters;
enum class Turn { WRITER_TURN, READER_TURN };
Turn turn;
void reader() {
while (1) {
{
std::unique_lock<std::mutex> lk(readerMutex);
while (turn != Turn::READER_TURN) {
readerCv.wait(lk);
}
std::cout << "Thread : " << std::this_thread::get_id() << " consumed " << data.front() << std::endl;
data.pop();
if (data.empty()) {
turn = Turn::WRITER_TURN;
writerCv.notify_one();
}
}
}
}
void writer() {
while (1) {
{
std::unique_lock<std::mutex> lk(writerMutex);
while (turn != Turn::WRITER_TURN) {
writerCv.wait(lk);
}
srand(time(NULL));
int random_number = std::rand();
data.push(random_number);
std::cout << "Thread : " << std::this_thread::get_id() << " produced " << random_number << std::endl;
turn = Turn::READER_TURN;
}
readerCv.notify_one();
}
}
public:
ReaderWriter(size_t noReadersArg, size_t noWritersArg) : noReaders(noReadersArg), noWriters(noWritersArg), turn(ReaderWriter::Turn::WRITER_TURN) {
}
void run() {
int noReadersArg = noReaders, noWritersArg = noWriters;
while (noReadersArg--) {
readers.emplace_back(&ReaderWriter::reader, this);
}
while (noWritersArg--) {
writers.emplace_back(&ReaderWriter::writer, this);
}
}
~ReaderWriter() {
for (auto& r : readers) {
r.join();
}
for (auto& w : writers) {
w.join();
}
}
};
int main() {
ReaderWriter<int> rw(5, 5);
rw.run();
}
答案 2 :(得分:3)
这是一段代码片段。由于工人踏板已经同步,因此排除了对两个缓冲器的要求。因此,使用简单队列来模拟场景:
cond = lambda l: l.startswith("Percentage of adjacent deep bass") or l.startswith("Loading")
lines = [line for line in text.strip().split("\n") if cond(line)]
>>> lines
["Loading /Volumes/My Passport for Mac/Jonas's iTunes/#4 falling.mp3",
'Percentage of adjacent deep bass frames: 0.125822 ',
"Loading /Volumes/My Passport for Mac/Jonas's iTunes/#OccupyHipHop Inst.mp3",
'Percentage of adjacent deep bass frames: 0.300660 ']
答案 3 :(得分:3)
虽然我认为两个条件变量可能有用,一个名为buffer_empty
,生产者线程将等待,另一个名为buffer_filled
,消费者线程将等待,但不是一个完整的答案。互斥锁的数量,如何循环等等我无法评论,因为我自己不确定细节。
答案 4 :(得分:2)
condition_variable::wait
应检查一个条件。
condition_variable::wait
的互斥锁保护的共享变量。wait
或使用wait
的2参数重载(这是
相当于while循环版本)注意:如果您真正了解硬件正在做什么,则这些规则并非绝对必要。但是,当使用简单的数据结构时,这些问题会很快变得复杂,如果您遵循它们,将更容易证明您的算法正常工作。
您的Q
和Q_buf
是共享变量。由于规则1,我更愿意将它们作为在使用它们的函数中声明的局部变量(分别为consume()
和produce()
)。将有一个受互斥锁保护的共享缓冲区。生产者将添加到其本地缓冲区。当该缓冲区已满时,它将获取互斥锁并将本地缓冲区推送到共享缓冲区。然后,它会在生成更多数据之前等待消费者接受此缓冲区。
使用者等待此共享缓冲区“到达”,然后它获取互斥锁并用共享缓冲区替换其空本地缓冲区。然后它向生产者发出信号,告知缓冲区已被接受,因此它知道再次开始生产。
从语义上讲,我没有理由使用swap
而不是move
,因为在每种情况下,其中一个容器都是空的。也许你想使用swap
因为你对底层内存有所了解。你可以使用你想要的任何一个,它会很快并且工作方式相同(至少在算法上)。
这个问题可以通过1个条件变量来完成,但如果使用2,可能会更容易思考。
这就是我想出的。在Visual Studio 2017(15.6.7)和GCC 5.4.0上测试。我不需要记入任何东西(这是一件如此简单的作品),但从法律上讲,我不得不说我不提供任何保证。
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <chrono>
std::vector<int> g_deliveryBuffer;
bool g_quit = false;
std::mutex g_mutex; // protects g_deliveryBuffer and g_quit
std::condition_variable g_producerDeliver;
std::condition_variable g_consumerAccepted;
// consumer
void consume()
{
// local buffer
std::vector<int> consumerBuffer;
while (true)
{
if (consumerBuffer.empty())
{
std::unique_lock<std::mutex> lock(g_mutex);
while (g_deliveryBuffer.empty() && !g_quit) // if we beat the producer, wait for them to push to the deliverybuffer
g_producerDeliver.wait(lock);
if (g_quit)
break;
consumerBuffer = std::move(g_deliveryBuffer); // get the buffer
}
g_consumerAccepted.notify_one(); // notify the producer that the buffer has been accepted
// for-loop to process the elems in Q
// ...
consumerBuffer.clear();
// ...
}
}
// producer
void produce()
{
std::vector<int> producerBuffer;
while (true)
{
// for-loop to fill up Q_buf
// ...
producerBuffer = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// ...
// once Q_buf is fully filled, wait until consumer asks to give it a full Q
{ // scope is for lock
std::unique_lock<std::mutex> lock(g_mutex);
g_deliveryBuffer = std::move(producerBuffer); // ok to push to deliverybuffer. it is guaranteed to be empty
g_producerDeliver.notify_one();
while (!g_deliveryBuffer.empty() && !g_quit)
g_consumerAccepted.wait(lock); // wait for consumer to signal for more data
if (g_quit)
break;
// We will never reach this point if the buffer is not empty.
}
}
}
int main()
{
// spawn threads
std::thread consumerThread(consume);
std::thread producerThread(produce);
// for for 5 seconds
std::this_thread::sleep_for(std::chrono::seconds(5));
// signal that it's time to quit
{
std::lock_guard<std::mutex> lock(g_mutex);
g_quit = true;
}
// one of the threads may be sleeping
g_consumerAccepted.notify_one();
g_producerDeliver.notify_one();
consumerThread.join();
producerThread.join();
return 0;
}