【问题标题】:Consumer thread is not synchronized until producer finishes its execution消费者线程在生产者完成执行之前不会同步
【发布时间】:2018-03-28 01:03:07
【问题描述】:

我正在学习 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 线程,但几个小时后我仍然不明白为什么producer 没有唤醒@ 987654329@直到结束。

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

void producerer(std::vector<int>& v);
void consumer(std::vector<int>& v);

std::vector<int> numbers;
std::thread t(producer, std::ref(numbers));
std::thread t2(consumerer, std::ref(numbers));

操作系统: Debian 9

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

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

【问题讨论】:

  • produce 保持互斥锁不断锁定,但只是最短暂的时刻。 consume 根本没有足够快地醒来,并且渴望访问。它只在produce 完成后才设法获取互斥锁。

标签: multithreading c++14 producer-consumer


【解决方案1】:

这里的问题是,当生产者释放锁时,您期望消费者接下来获取锁。但是,由于生产者中的 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)
...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-10
    • 1970-01-01
    • 1970-01-01
    • 2014-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多