简体   繁体   中英

unique_lock across various threads

I have a consumer and two producers. When i spawn both producers at the same time, they seem to lock each other out as the first values we see are 223 and 889 from each.

could somebody please explain what is happening here?

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

using namespace std;

vector<double>testvec;
mutex mtx;
condition_variable cv;

class Base
{
public:
    Base() {};
    void dosomething();
    int i;
};

void Base::dosomething()
{
    while(1)
    {
        std::unique_lock<std::mutex> ulck(mtx);
        testvec.push_back(i);
        ulck.unlock();
        cv.notify_all();
        i++;
        std::this_thread::sleep_for (std::chrono::milliseconds(i));
    }
};

class Derived1 : public Base
{
public:
    Derived1() {i = 222;}
};

class Derived2 : public Base
{
public:
    Derived2() {i = 888;}
};

class Consumer
{
public:
    Consumer() {}
    void dostuff();
};

void Consumer::dostuff()
{
    while(1)
    {
        std::unique_lock<std::mutex> ulck(mtx);    //locks shared data
        cv.wait(ulck);
        cout<<"last value: "<<testvec.back()<<endl;
        ulck.unlock();
    }
}

int main( int argc, char ** argv )
{
    Derived1 derived1;
    Derived2 derived2;
    Consumer c;

    std::thread t1(&Derived1::dosomething, &derived1);
    std::thread t2(&Derived2::dosomething, &derived2);
    std::thread t3(&Consumer::dostuff, &c);

    t1.join();
    t2.join();
    t3.join();
}

output is:
last value: 223
last value: 224
last value: 225
last value: 889
last value: 226
last value: 227
last value: 228
last value: 229
last value: 890
last value: 230

expected output:
last value: 888 (or 222)
last value: 222 (or 888)
last value: 223
...

You will never get sane behavior from wait without a predicate. Also, why do you keep releasing the lock only to immediately lock it again?

This is what you ened to do:

void Consumer::dostuff()
{
    std::unique_lock<std::mutex> ulck(mtx);    //locks shared data
    int i = 0;
    while(1)
    {

        // Correctly decide when to wait and when to stop waiting
        while (testvec.empty() || (testvec.back() == i))
            cv.wait(ulck);

        i = testvec.back();
        cout<<"last value: "<<testvec.back()<<endl;
    }
}

Condition variables are stateless and have no way to know whether they should wait or when they should stop waiting as those are functions of the shared state. It's your responsibility to code that, using the mutex to protect the shared state.

Notice this code only calls wait if it has to wait. You can only tell if you need to wait by inspecting the shared state. And notice it continue to call wait while it has to wait. Again, you can only tell if you can stop waiting by inspecting the shared state. The shared state is your responsibility -- condition variables are stateless.

I have a consumer and two producers.

Wrong. You have 2 producers and a sampler. Nothing is consuming.

What you probably want is this (after simplification):

#include<deque>
#include<thread>
#include<iostream>
#include<mutex>
#include<chrono>
#include <condition_variable>

using namespace std;

deque<double> testvec;
mutex mtx;
condition_variable cv;

auto producer = [](int i)
{
    while(1)
    {
        std::unique_lock<std::mutex> ulck(mtx);
        testvec.push_back(i);
        ulck.unlock();
        cv.notify_all();
        i++;
        std::this_thread::sleep_for (std::chrono::milliseconds(i));
    }
};

void consume()
{
    while(1)
    {
        std::unique_lock<std::mutex> ulck(mtx);    //locks shared data


        // wait until there is something to consume...
        cv.wait(ulck, [&]{return not testvec.empty(); });

        /// ... consume it...
        auto val = testvec.front();
        testvec.pop_front();

        /// ... now unlock and work with the consumed data while unlocked
        ulck.unlock();

        /// ... do work
        cout<<"last value: " << val <<endl;
    }
}

int main( int argc, char ** argv )
{
    std::thread t1(producer, 222);
    std::thread t2(producer, 888);
    std::thread t3(consume);

    t1.join();
    t2.join();
    t3.join();
}

example output:

last value: 222
last value: 888
last value: 223
last value: 224
last value: 225
last value: 889
last value: 226
...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM