Multithreading 在生产者完成其执行之前,使用者线程不会同步 我正在学习C++(14),目前我正在处理并发部分。 我写了一个小的生产者/消费者示例来学习和理解条件变量

Multithreading 在生产者完成其执行之前,使用者线程不会同步 我正在学习C++(14),目前我正在处理并发部分。 我写了一个小的生产者/消费者示例来学习和理解条件变量,multithreading,c++14,producer-consumer,Multithreading,C++14,Producer Consumer,其思想是一个线程用数字填充向量,然后另一个线程将其打印到控制台: #include <iostream> #include <thread> #include <condition_variable> #include <vector> #include <atomic> std::atomic<bool> done{false}; std::mutex ready_mutex; std::condition_variab

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

#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;
}
但我得到的是:

(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)
就我所见,
生产者
线程一次性运行所有迭代,然后通知
消费者
线程,但几个小时后,我仍然不明白为什么生产者直到最后才唤醒消费者

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

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));
void生产者(std::vector&v);
无效消费者(标准::矢量和v);
std::向量数;
标准::螺纹t(生产者,标准::参考(编号));
标准::线程t2(用户,标准::参考(编号));

OS:Debian 9

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


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

这里的问题是,当生产者释放锁时,您希望消费者下一步获得锁。但是,由于生产者中的while循环,生产者有可能在使用者可以重新获取锁之前重新获取锁。如果制作人只“生产”一次,然后向消费者让步,这不会有问题,但因为你想要乒乓球效果,你需要确保制作人以消费者等待轮到他的方式等待轮到他

#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;
}
解决这个问题的一种方法是使用另一个布尔值。我将其称为
canproduction
,并以与设置消费者相同的方式设置生产者。我还将主要重复整个过程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;
}

product
使互斥锁始终处于锁定状态,但锁定时间最短<代码>消费只是没有足够快地醒来,并且渴望访问。它只会在
product
完成后获取互斥锁。
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)
...