Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Blocking condition variables

117 views
Skip to first unread message

Rainer Grimm

unread,
May 8, 2012, 6:11:19 PM5/8/12
to
{ Please limit your text to fit within 80 columns, preferably around 70,
so that readers don't have to scroll horizontally to read each line.
This article has been reformatted manually by the moderator. -mod }

Hello,
I have written a program, which will describe a workflow synchronized by
condition variables. The idea is simple. Two workers notify the boss
(notify_one), when their step of work is done. As soon as the boss gets
the two notifications, he notifys (notify_all) both worker, so that they
can proceed.
These interaction will take place two times. The first time, the workers
have to prepare their work. The second time, the workers have to do their
work.
In order to get right number of notifications, the boss use atomic
variables.
And here is the program.
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <random>
#include <thread>


// Boss -> Worker
bool startWork, goHome;

std::mutex startWorkMutex, goHomeMutex;

std::condition_variable boss2WorkerCondVariable;

// Worker -> Boss
std::mutex preparedMutex, doneMutex;

std::condition_variable worker2BossCondVariable;

std::atomic_int preparedCount, doneCount;


int getRandomTime(int start, int end){

std::random_device seed;
std::mt19937 engine(seed());
std::uniform_int_distribution<int> dist(start,end);

return dist(engine);
};

class Worker{
public:
Worker(std::string n):name(n){};

void operator() (){
// prepare the work and notfiy the boss
int prepareTime= getRandomTime(500,2000);
std::this_thread::sleep_for(std::chrono::milliseconds(prepareTime));
preparedCount++;
std::cout << name << ": " << prepareTime << std::endl;
worker2BossCondVariable.notify_one();

// { scope seems to be necessary
// wait for the start notification of the boss
std::unique_lock<std::mutex> startWorkLock( startWorkMutex );
boss2WorkerCondVariable.wait( startWorkLock,[]{ return startWork;});
// }

// do the work and notify the boss
int workTime= getRandomTime(200,400);
std::this_thread::sleep_for(std::chrono::milliseconds(workTime));
doneCount++;
std::cout << name << ": " << workTime << std::endl;
worker2BossCondVariable.notify_one();

// { scope seems to be necessary
// wait for the go home notification of the boss
std::unique_lock<std::mutex> goHomeLock( goHomeMutex );
boss2WorkerCondVariable.wait( goHomeLock,[]{ return goHome;});
// }

}
private:
std::string name;
};

int main(){

Worker worker1(" Worker1");
std::thread worker1Work(worker1);

Worker worker2(" Worker2");
std::thread worker2Work(worker2);

std::cout << "BOSS: PREPARE YOUR WORK.\n " << std::endl;

// waiting for the worker
preparedCount.store(0);
std::unique_lock<std::mutex> preparedUniqueLock( preparedMutex );
worker2BossCondVariable.wait(preparedUniqueLock,[]{ return preparedCount == 2; });

// notify the worker about the begin of the work
startWork= true;
std::cout << "\nBOSS: START YOUR WORK. \n" << std::endl;
boss2WorkerCondVariable.notify_all();

// waiting for the worker
doneCount.store(0);
std::unique_lock<std::mutex> doneUniqueLock( doneMutex );
worker2BossCondVariable.wait(doneUniqueLock,[]{ return doneCount == 2; });

// notify the worker about the end of the work
goHome= true;
std::cout << "\nBOSS: GO HOME. \n" << std::endl;
boss2WorkerCondVariable.notify_all();

worker1Work.join();
worker2Work.join();

}
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
In case I'm executing the program, the condition variables will block:

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
BOSS: PREPARE YOUR WORK.

Worker1: 658
Worker2: 1836

BOSS: START YOUR WORK.

Worker1: 270
^C
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
In case I'm using the artificial scope in the the functor (look at the
source code), the program behave in the expected way.

BOSS: PREPARE YOUR WORK.

Worker1: 650
Worker2: 1507

BOSS: START YOUR WORK.

Worker1: 201
Worker2: 364

BOSS: GO HOME.

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

This behaviour is happening with GCC4.6 and also with GCC4.7. I have no
idea why is is necessary to control the lifetime of the unique_lock in
den body of the functor.

Kindly regards from Rottenburg,
Rainer Grimm


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

K. Frank

unread,
May 9, 2012, 3:04:34 AM5/9/12
to
Hello Rainer!

I think I see your issue -- in-line below.

On May 8, 6:11 pm, Rainer Grimm <r.gr...@science-computing.de> wrote:
> ...
> Hello,
> I have written a program, which will describe a workflow synchronized by
> condition variables. The idea is simple. Two workers notify the boss
> (notify_one), when their step of work is done. As soon as the boss gets
> the two notifications, he notifys (notify_all) both worker, so that they
> can proceed.
> ...
> And here is the program.
> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
> ...
> class Worker{
> public:
> Worker(std::string n):name(n){};
>
> void operator() (){
> // prepare the work and notfiy the boss
> int prepareTime= getRandomTime(500,2000);
> std::this_thread::sleep_for(std::chrono::milliseconds(prepareTime));
> preparedCount++;
> std::cout << name << ": " << prepareTime << std::endl;
> worker2BossCondVariable.notify_one();

Just a comment here: This isn't your problem, but there is a
potential race condition here. (In practice, however, this sample
program, because of the sleeps in the worker threads won't have this
potential race.) The problem is that the worker thread might call

worker2BossCondVariable.notify_one();

before the parent thread waits on the condition variable, thereby
causing the parent thread to miss the notification, and not unblock.

>
> // { scope seems to be necessary
> // wait for the start notification of the boss
> std::unique_lock<std::mutex> startWorkLock( startWorkMutex );
> boss2WorkerCondVariable.wait( startWorkLock,[]{ return startWork;});

Here's the problem: As the condition-variable wait

boss2WorkerCondVariable.wait( startWorkLock,[]{ return
startWork;});

returns, the startWork mutex gets reacquired. Without what you term
the "artificial scope," that mutex stays acquired, blocking the second
worker thread. When you put

std::unique_lock<std::mutex> startWorkLock( startWorkMutex );

inside of the "artificial scope," then when the startWorkLock is
destroyed at the end of the "artificial scope" the mutex is
released (that's a big part of what unique_lock is used for),
unblocking the second worker thread.

> // }

This is the end-of-scope that causes the unique_lock to be destroyed
and the mutex released (if the scope weren't commented out).

> ...
> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
> In case I'm executing the program, the condition variables will block:
>
> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
> BOSS: PREPARE YOUR WORK.
>
> Worker1: 658
> Worker2: 1836
>
> BOSS: START YOUR WORK.
>
> Worker1: 270
> ^C
> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
> In case I'm using the artificial scope in the the functor (look at the
> source code), the program behave in the expected way.
>
> BOSS: PREPARE YOUR WORK.
>
> Worker1: 650
> Worker2: 1507
>
> BOSS: START YOUR WORK.
>
> Worker1: 201
> Worker2: 364
>
> BOSS: GO HOME.
>
> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

As far as I can tell the above analysis is completely consistent with
the results you see.

>
> This behaviour is happening with GCC4.6 and also with GCC4.7. I have no
> idea why is is necessary to control the lifetime of the unique_lock in
> den body of the functor.

As mentioned above, it's the destruction of the unique_lock triggered
by the end of scope that releases the mutex.

I think putting scope blocks around the use of unique_lock (and
lock_guard) is a pretty standard technique.

> Kindly regards from Rottenburg,
> Rainer Grimm

Good luck!


K. Frank

Rainer Grimm

unread,
May 9, 2012, 5:58:26 PM5/9/12
to
Hello,
thank you for your great explanation.
> Just a comment here: This isn't your problem, but there is a
> potential race condition here. (In practice, however, this sample
> program, because of the sleeps in the worker threads won't have this
> potential race.) The problem is that the worker thread might call
That is the more challenging problem. I see now simple solution to the race
condition. My first idea was it to use a own thread for the Boss.
I will sketch my idea.
- start the Boss in his own Thread
- wait in the main-Thread until the Boss is blocking
- start each Worker in his own Thread
But, how can I be sure, that the Boss-Thread is in the waiting state. So I'm
looking for a possibility to ask the condition variable for it's state. But
there is now such method available for a condition variable.
Any idea how to solve this issue in a nifty way.
> As mentioned above, it's the destruction of the unique_lock triggered
> by the end of scope that releases the mutex.
>
> I think putting scope blocks around the use of unique_lock (and
> lock_guard) is a pretty standard technique.
You are right. It's nice application of the RAII-Idiom. So you can be sure,
that you don't forget to unlock the mutex.

Kindly regards from Rottenburg,
Rainer Grimm


K. Frank

unread,
May 11, 2012, 12:25:13 AM5/11/12
to
Hi Rainer!

On May 9, 5:58 pm, Rainer Grimm <r.gr...@science-computing.de> wrote:
> Hello,
> > Just a comment here: This isn't your problem, but there is a
> > potential race condition here. (In practice, however, this sample
> > program, because of the sleeps in the worker threads won't have
> > this potential race.) The problem is that the worker thread might
> > call
>

> That is the more challenging problem. I see now simple solution to
> the race condition. My first idea was it to use a own thread for the
> Boss.
> I will sketch my idea.
> - start the Boss in his own Thread
> - wait in the main-Thread until the Boss is blocking
> - start each Worker in his own Thread
> But, how can I be sure, that the Boss-Thread is in the waiting
> state. So I'm looking for a possibility to ask the condition
> variable for it's state. But there is now such method available for
> a condition variable. Any idea how to solve this issue in a nifty
> way.

My first question is whether your goal here is to experiment with ways
to use condition variables, or whether you have a specific practical
problem you're trying to solve.

If the latter, then perhaps you could tell us what you're trying to
accomplish, and maybe there are already some standard techniques for
doing what you want to do.

The short answer -- if I understand what you're trying to do in your
test program -- is that when you find yourself wanting to query a
condition variable for its state, you should probably introduce an
additional variable (or variables) that describe the state you want to
query. This could be as simple as a single boolean flag.

Usually those variables will be protected / synchronized by a mutex --
presumably the mutex that is associated with the condition variable.

But again, if you give us more detail on what you want to -- either
just experimenting with condition variables or a practical problem you
need to solve -- we can probably help out with more specific answers.

> ...
> Kindly regards from Rottenburg,
> Rainer Grimm

Best.

K. Frank

Rainer Grimm

unread,
May 11, 2012, 6:19:32 AM5/11/12
to
Hello,
> My first question is whether your goal here is to experiment with ways
> to use condition variables, or whether you have a specific practical
> problem you're trying to solve.
the later one. I'm playing with condition variables. And my simple idea was it
to use them to describe a workflow.
>
> If the latter, then perhaps you could tell us what you're trying to
> accomplish, and maybe there are already some standard techniques for
> doing what you want to do.
>
> The short answer -- if I understand what you're trying to do in your
> test program -- is that when you find yourself wanting to query a
> condition variable for its state, you should probably introduce an
> additional variable (or variables) that describe the state you want to
> query. This could be as simple as a single boolean flag.
So I have to protect the interaction of the threads int the common case with
two state variables.
- the waiting-Thread has to be sure, that no spurious wakeup appear
- the notfiying-Thread has to to be sure, the the waiting thread is ready
I will rebuild the workflow and using C++11 futures with wait calls. So there
is blocking behavior involved and I get the synchronization for free.
>
> Usually those variables will be protected / synchronized by a mutex --
> presumably the mutex that is associated with the condition variable.
>
> But again, if you give us more detail on what you want to -- either
> just experimenting with condition variables or a practical problem you
> need to solve -- we can probably help out with more specific answers.
Thanks for your answers. Know I am aware that more things can go wrong as I
thought of before. Especially that the notifying-Thread can overtake the waiting-Thread.

Rainer Grimm

K. Frank

unread,
May 11, 2012, 8:41:01 AM5/11/12
to
Hi Rainer!

On May 11, 6:19 am, Rainer Grimm <r.gr...@science-computing.de> wrote:
> ...
> So I have to protect the interaction of the threads int the common case
with
> two state variables.
> - the waiting-Thread has to be sure, that no spurious wakeup appear
> - the notfiying-Thread has to to be sure, the the waiting thread is ready
> I will rebuild the workflow and using C++11 futures with wait calls. So
there
> is blocking behavior involved and I get the synchronization for free.

Yes, this sounds good. Depending on the details of what you
are trying to do, this could be the way to go.

Futures are a "higher-level abstractions," so to speak, so
if they fit what you're doing they should be more convenient
to use than implementing the functionality with lower-level
constructs.

(I haven't looked at the implementation of std::future, but
I'm pretty sure it uses condition variables under the hood.)

> ...
> Rainer Grimm

Happy Hacking!


K. Frank
0 new messages