生产者-消费者的特殊情况

时间:2019-09-02 15:21:23

标签: c++ multithreading synchronization producer-consumer

我需要实现一个特殊的生产者-消费者方案,其中类Consumer创建两个处理两个端口的线程,每个线程将一个值存储到相应的端口中,如果另一个端口上有值,则{{1}调用}消耗两个端口上的值。这是我的尝试:

process

以下是输出:

#include <thread>
#include <atomic>
#include <condition_variable>
#include <vector>
#include <iostream>
#include <mutex>

struct Port {
    int value;
    std::atomic<bool> free;
};

class Consumer {
private:
    Port port1;
    Port port2;
    std::mutex mutex;
    std::condition_variable port1_ready;
    std::condition_variable port2_ready;

    std::vector<std::thread> workers;
    std::atomic<bool> done;
    int count;

public:
    Consumer() : done(false), count(0) {
        port1.free = true;
        port1.value = 0;
        port2.free = true;
        port1.value = 0;

        workers.push_back(std::thread([this]{
            while (!done) {
                write1(rand());
            }
        }));

        workers.push_back(std::thread([this]{
            while (!done) {
                write2(rand());
            }
        }));
    }

    void write1(int value) {
        std::unique_lock lock(mutex);
        port1.value = value;
        port1.free = false;
        std::cout << "port1 stored " << value << std::endl;
        port1_ready.notify_one();
        if (port2.free) {
            port2_ready.wait(lock);
        }
        process("port1");
    }

    void write2(int value) {
        std::unique_lock lock(mutex);
        port2.value = value;
        port2.free = false;
        std::cout << "port2 stored " << value << std::endl;
        port2_ready.notify_one();
        if (port1.free) {
            port1_ready.wait(lock);
        }
        process("port2");
    }

    void process(std::string string) {
        port1.free = true;
        port2.free = true;
        std::cout << string << " consumed " << port1.value << " " << port2.value << std::endl;
        if (count++ == 20) done = true;
    }

    ~Consumer() {
        for (auto& w: workers) {
            w.join();
        }
    }
};

int main(int argc, char** argv) {

    Consumer c{};

    return 0;
}

有时它将成功返回,而其他则会陷入僵局。

2 个答案:

答案 0 :(得分:1)

注意以下几点可以很容易地发现您的逻辑错误:

void process(std::string string) {
        port1.free = true;
        port2.free = true;

这清楚地表明,您的意图是考虑现在放置在两个“端口”中要进行“处理”的值。也就是说,一旦在两个“端口”中都放置了一个值,则两个值都将被“处理”,并且两个端口都将再次“空闲”。

但是,请注意日志的开头:

port1 stored 41
port2 stored 41
port2 consumed 41 41

到目前为止,两个端口中均放置了41个端口,port2进程最终对其进行了“处理”。但是之后:

port2 stored 18467
port1 consumed 41 18467

在这一点上,事情几乎已经脱离了轨道。 41已经被“处理”了,显然不应该再次“处理”。

在两张纸上打印write1()write2()的内容。使用左手的食指来跟踪write1()线程的执行,并用右手的食指来跟踪write2()线程的执行。

从您的右手开始,跟踪write2(),因为它锁定了互斥锁并开始了业务,并发现了这个

    if (port2.free) {

是真的,然后

        port2_ready.wait(lock);

等待该条件变量。这样可以解除互斥锁的阻塞,并且您的左手食指现在可以向前移动。现在,您的左手食指向前移动,直到:

  port2_ready.notify_one();

这会通知另一个线程,该线程必须等待互斥锁被解锁,因此它在右手食指继续移动时等待:

  if (port1.free) {

这是真的,因此现在第一个线程可以移动了。而且,如果您继续进行下去,您将看到两个线程如何最终进入process(),而不仅仅是其中一个。失败。

此逻辑从根本上被破坏了。有几种正确执行此操作的方法,但是最简单的方法如下。当任一线程获取互斥锁时,它就会

  • 检查线程拥有的端口中是否已经有值(从上次被调用开始)。

  • 如果端口已经有一个值,请等待条件变量,直到该端口空闲为止(依靠另一个线程将其清除)。

  • 如果端口空闲,则将其值保存在线程的端口中,然后检查另一个线程的端口是否已具有值。如果没有,则该线程可以返回并继续其业务以获取下一个值,并确保另一个线程将处理两个保存的值。

  • 否则,两个端口都有值,调用process()来处理它们,清除两个端口,并用信号通知另一个线程的条件变量,让它知道如果它正在等待其端口空闲,则它是现在免费用于保存另一个线程的下一个值。

答案 1 :(得分:1)

即使processport1.free中的一个为真,您也会呼叫port2.free

您可以将代码更改为:

struct Port {
    std::optional<int> value;
};

class Consumer {
private:
    Port port1;
    Port port2;
    std::mutex mutex;
    std::condition_variable port1_ready;
    std::condition_variable port2_ready;
    std::vector<std::thread> workers;
    std::atomic<bool> done;
    int count;

public:
    Consumer() : done(false), count(0) {
        std::random_device rd;

        workers.push_back(std::thread([this, gen = std::mt19937{rd()}] () mutable {
            while (!done) {
                write1(gen());
            }
        }));

        workers.push_back(std::thread([this, gen = std::mt19937{rd()}] () mutable {
            while (!done) {
                write2(gen());
            }
        }));
        port1_ready.notify_one();
        port2_ready.notify_one();
    }

    void write1(int value) {
        std::unique_lock lock(mutex);
        port1_ready.wait(lock, [&](){ return !port1.value; });

        port1.value = value;
        std::cout << "port1 stored " << value << std::endl;
        if (port2.value) {
            process("port1");
        }
    }

    void write2(int value) {
        std::unique_lock lock(mutex);
        port2_ready.wait(lock, [&](){ return !port2.value; });
        port2.value = value;

        std::cout << "port2 stored " << value << std::endl;
        if (port1.value) {
            process("port2");
        }
    }

    void process(std::string string) {
        std::cout << string << " consumed " << *port1.value << " " << *port2.value << std::endl;
        port1.value.reset();
        port2.value.reset();
        port1_ready.notify_one();
        port2_ready.notify_one();
        if (count++ == 20) done = true;
    }

    ~Consumer() {
        for (auto& w: workers) {
            w.join();
        }
    }
};

int main() {
    Consumer c{};
}

Demo

相关问题