lundi 4 juillet 2016

Protecting std::condition_variable and shared data

Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread. Any thread that intends to wait on std::condition_variable has to acquire a std::unique_lock, on the same mutex as used to protect the shared variable

http://ift.tt/1olYwuW

I understand that by using the same mutex we are protected against missing a notify if the waiting thread is not actually waiting. Answered here already: Shared atomic variable is not properly published if it is not modified under mutex

If it's possible, I'd like to protect only the std::condition_variable by a common mutex, keeping the protection of shared data more abstract, by using separate mutexes or atomic variables controlled by other classes.

If we modify the example given in the other answer, would this work?

std::atomic_bool proceed(false);
std::mutex m;
std::condition_variable cv;

std::thread t([&m,&cv,&proceed]()
{
    {
        std::unique_lock<std::mutex> l(m);
        while(!proceed) {
            hardWork();
            cv.wait(l);
        }
    }
});

proceed = true;

{
    std::lock_guard<std::mutex> lock(m);
}
cv.notify_one();
t.join();

Or is there something going on with memory ordering or caches that I have missed?

If someone is interested where I'm going to use this, here is a simplified version. A queue handler where blocking time before notifying is kept down to a minimum.

std::mutex mutexConditionVariable;
std::unique_lock<std::mutex> lock(mutexConditionVariable);
std::condition_variable conditionVariable;

Class1 obj1(mutexConditionVariable, conditionVariable);
Class2 obj2(mutexConditionVariable, conditionVariable);


void processQueue1()
{
    while (obj1.condition1())
    {
        lock.unlock();
        /*
            Do something very time consuming
            Notifications might be missed while unlocked
        */
        lock.lock();
    }
}

void processQueue2()
{
    while (obj2.condition2())
    {
        lock.unlock();
        /*
            Do something very time consuming
            Notifications might be missed while unlocked
        */
        lock.lock();
        processQueue1(); //What if something was added to queue1? Better check
    }
}

int main()
{
    std::thread t1(&Class1::AddToQueue, obj1);
    std::thread t2(&Class2::AddToQueue, obj2);

    while (true)
    {
        processQueue1();
        processQueue2();
        conditionVariable.wait(lock);
    }

    t1.join();
    t2.join();

    return 0;
}

Obj1.condition1() and Obj2.condition2() are using separate ways of protecting shared data so that they can both add to their respective queues simultaneously.

Aucun commentaire:

Enregistrer un commentaire