在生产者完成执行之前,消费者线程不会同步

时间:2018-03-28 01:03:08

标签: multithreading c++14 producer-consumer

我正在学习C ++(14),目前我正在研究并发部分。 我已经编写了一个小producer/consumer示例来学习和理解condition_variables

这个想法是让一个线程用数字填充一个向量,然后用另一个线程将它打印到控制台:

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

std::atomic<bool> done{false};
std::mutex ready_mutex;
std::condition_variable ready;
std::vector<int> numbers;

void produce() {
    auto idx = 0;
    while( true ) {
        ++idx;
        std::lock_guard<std::mutex> lk(ready_mutex);

        for(int i = 0; i < 5; ++i) {
            numbers.push_back(i);
        }

        if( 5 == idx) {
            done = true;
            break;
        }

        ready.notify_one();
    }
    ready.notify_one();
}

void consume() {
    while( true ) {
        std::unique_lock<std::mutex> lk(ready_mutex);
        ready.wait(lk, [](){ return !v.empty(); });

        std::cout << "(" << numbers.size() << ")" << std::endl;
        for(auto x: numbers) {
            std::cout << x << ", ";
        }
        std::cout << std::endl;

        numbers.clear();
        std::cout << "(" << numbers.size() << ")" << std::endl;
        std::cout.flush();

        if(done) break;
    }
}

int main() {
    std::thread t(produce);
    std::thread t2(consume);

    t.join();
    t2.join();

    return 0;
}

问题是该程序没有向我显示预期的输出。 我期待的是:

(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)

但我得到的是:

(25)
0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4,
(0)

对于我所看到的,producer线程一次性运行所有迭代,然后通知consumer线程,但几个小时后我仍然不明白为什么{{1直到最后都没有唤醒producer

此外,我注意到如果不使用全局向量而将此问题作为对线程的引用传递,则此问题仍然存在:

consumer

操作系统: Debian 9

编译器: g ++ 6.3.0,clang ++ 3.8.1-24

编译标志: -Werror -Wextra -Wall -std = c ++ 14 -O0 -g3 -pthread

1 个答案:

答案 0 :(得分:0)

这里的问题是当生产者释放锁时,你期望消费者接下来获得锁。但是,由于生产者中的while循环,生产者可能会在消费者可以之前重新获得锁定。如果生产者只“生产”一次然后屈服于消费者,那就不会有问题,但由于你想要乒乓效应,你需要确保生产者等待消费者等待消费者等待的方式轮到它了。

解决这个问题的一种方法是使用另一个布尔值。我将其命名为canProduce,并以与设置消费者相同的方式设置生产者。我还将在主要内容中重复整个过程15次,以最大限度地减少侥幸误报的可能性,如果制作人仍然没有正确地等待它。

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

std::atomic<bool> done{false};
std::atomic<bool> canProduce{true};
std::mutex ready_mutex;
std::condition_variable ready;
std::vector<int> numbers;

void produce() {
    auto idx = 0;
    while( true ) {
        ++idx;
        std::unique_lock<std::mutex> lk(ready_mutex);
        ready.wait(lk, [](){ return canProduce == true; });

        for(int i = 0; i < 5; ++i) {
            numbers.push_back(i);
        }

        canProduce = false;

        // Manually unlocking thread to ensure that when the consumer thread
        // wakes up, this thread is unlocked and the consumer thread can acquire
        // the lock, otherwise it'll go back to sleep.
        lk.unlock();
        ready.notify_one();

        if( 5 == idx) {
            done = true;
            break;
        }
    }
}

void consume() {
    while( true ) {
        std::unique_lock<std::mutex> lk(ready_mutex);
        ready.wait(lk, [](){ return !numbers.empty(); });

        std::cout << "(" << numbers.size() << ") ";
        for(auto x: numbers) {
            std::cout << x << ", ";
        }

        numbers.clear();
        std::cout << "(" << numbers.size() << ")" << std::endl;
        std::cout.flush();

        if (done) {
            break;
        } else {
            canProduce = true;
            lk.unlock();
            ready.notify_one();
        }
    }
}

int main() {
    for (int i = 0; i < 15; i++) {
        std::cout << "Attempt " << i << std::endl;
        std::thread t(produce);
        std::thread t2(consume);

        t.join();
        t2.join();

        done = false;
        canProduce = true;
    }

    return 0;
}

输出

Attempt 1
(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)
Attempt 2
(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)
(5) 0, 1, 2, 3, 4, (0)
...