views:

357

answers:

3

Hi Everyone, I wrote the following program for alternatively incrementing and doubling a counter(increment first) using boost condition variables. Can any one tell me if this is the correct use of boost condition variables. It is working correctly. I don't understand the use of lock in wait function call. What does condition.wait(lock) mean? For example what is the use of two scoped locks in increment and multiply in this program. How can I avoid them?

#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <boost/thread/locks.hpp>
#include <boost/thread/condition_variable.hpp>
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;

int counter=0;
boost::mutex m1,m2;
bool incremented=false,multiplied=false;
boost::condition_variable c1,c2;
void Increment()
{
    {
        boost::mutex::scoped_lock lk(m1);
        counter++;
        incremented = true;
        c1.notify_one();

        while(!multiplied)
            c2.wait(lk);
        multiplied=false;

    }   
}
void Multiply()
{
    {
        boost::mutex::scoped_lock lk(m2);
        while(!incremented)
            c1.wait(lk);
        incremented = false;
        counter = counter*2 ;
        multiplied = true;
        c2.notify_one();
    }
}

void IncrementNtimes(int n){

    for(int i=0;i<n;i++){
        Increment();
    }
}

void MultiplyNtimes(int n){

    for(int i=0;i<n;i++){
        Multiply();
    }
}
int main(int argc, char* argv[])
{
    srand ( time(NULL) );

    boost::thread thrd1(boost::bind(&IncrementNtimes,20));
    boost::thread thrd2(boost::bind(&MultiplyNtimes,20));
    thrd1.join();
    thrd2.join();
    cout<<"Main counter is:"<<counter<<endl;
    return 0;
}
A: 

Condition variable must always be associated with a lock. The point to realize is that you must be holding the lock to call wait(), but once inside wait() the lock is released. When you signal, you must also be holding the lock, and finally the wait wont return (even after the signal) until the signaler releases the lock.

dicroce
A: 

Locks are tied to condition variables since condition variables are stateless - if thread A signals when there are no waiters, and thread B then enters a wait, B will not be woken up. For this reason, there must be state that is tied to the condition variable as well (in this case, incremented and multiplied.) The lock protects this state being accessed across multiple threads. When you pass the lock to wait(), wait will atomically release the lock and wait on the condition variable, and reacquire the lock when the wait returns. This means that there is no window where the state behind the condition variable could have changed and the wait occurs.

For instance, if the condition variable weren't tied to a lock:

// In thread A
while(!incremented) 

// Context switch to Thread B:
incremented = true;

// Context switch to Thread A:) 
   c1.wait();  // Whoops, waiting for a condition that already happened.
Michael
Thanks a ton Michael. That really helped
Kamal
+1  A: 

No, this is not correct. You're almost there, but the big problem is that the Multiply and Increment functions should be using the same mutex.

A mutex is an object that provides MUTual EXclusion. In other words, the point of a mutex is to prevent two threads from touching the same variable at the same time and causing unpredictable results. A mutex is kind of like a token that one thread at a time holds that gives it the "right" to access a certain variable (or set of variables). In this case, the variable you are trying to protect is counter. There must be one and only one mutex that controls the right to access counter. In your case, each thread will hold its own token that it thinks gives it the right to access counter, and so there will be unpredictable behavior.

You "hold" a mutex by locking it. That's the point of the locks, and that's why you cannot "avoid" them. The entire point of the scoped locks is that, assuming you have only one mutex m, when one of the threads holds the lock on m, the other thread is guaranteed to not also be holding a lock on m. If you've coded correctly, holding a lock on m should be a prerequisite for accessing counter, and so the value of counter should be predictable.

Now, regarding the wait(). A call to wait() means "I give up the lock on this mutex until someone else signals this condition, and then I want it back". In the mean time, the thread stops. So assuming you have only one mutex m and a condition c, and lk is a lock on m, the line c.wait(lk) means that the thread will give up the lock lk on mand then suspend execution until some other thread calls c.notify_one() (or c.notify_all()). When the waiting thread returns from the call to wait(), it will have automatically re-gained the lock lk on m and so is permitted to access counter again.

Finally, these boost locks are "scoped" locks. This means that they are released automatically on destruction (when they go out of scope). So in this case, each function holds its lock until it exits, except for times when it has given up its lock to wait and has suspended execution pending a signal.

Tyler McHenry
Thanks a ton Tyler for that explanation.
Kamal