4

I am now testing std::condition_variable recently , and find it is quite different with pthread_cond_t after test , I like to know if anything in my test wrong ? or std::condition_variable is really quite different with pthread_cond_t ?

The pthread_cond_t source is the following , compiled at gcc 4.4.6 :

pthread_cond_t  condA  = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int ProcessRow = 0 ;
#define LOOPCNT 10

void *producer()
{
    int idx ;
    for(idx=0;idx<LOOPCNT;idx++)
    {
        //pthread_mutex_lock(&mutex);
        __sync_add_and_fetch(&ProcessRow,1) ;
        pthread_cond_signal(&condA);
        printf("sending signal...(%d)\n",ProcessRow) ;
        //pthread_mutex_unlock(&mutex);
    }
    printf("I am out ... \n") ;
}

void *consumer()
{
    int icnt = 0 ;
    while(1)
    {
        pthread_mutex_lock(&mutex);
        while (ProcessRow <= 0)
            pthread_cond_wait(&condA, &mutex);
        pthread_mutex_unlock(&mutex); // I forget to add unlock to fail this test
        __sync_sub_and_fetch(&ProcessRow,1) ;
        ++icnt ;
        printf("receving=(%d)\n",ProcessRow) ;
        usleep(10000) ;
    }
    printf("(%d)\n",ProcessRow) ;
}

The output :

sending signal...(1)
sending signal...(2)
sending signal...(3)
sending signal...(4)
sending signal...(5)
sending signal...(6)
sending signal...(7)
sending signal...(8)
sending signal...(9)
sending signal...(10)
I am out ...
receving=(9)

Look like comsumer thread block in pthread_cond_wait , so that "receving" only print one time !!!!

and then the following test is for std::condition_variable !!!!

The following binsem.hpp comes from https://gist.github.com/yohhoy/2156481
with a little modification , compiled at g++ 4.8.1

class binsem {
public:    
    explicit binsem(int init_count = count_max)      
   : count_(init_count) {}     
// P-operation / acquire    
void wait()    
{        
    std::unique_lock<std::mutex> lk(m_);        
    cv_.wait(lk, [this]{ return 0 < count_; });
    --count_;    
}    
bool try_wait()    
{        
    std::lock_guard<std::mutex> lk(m_);        
    if (0 < count_) 
    {            
        --count_;            
        return true;        
    } else 
    {            
        return false;        
    }    
}
// V-operation / release    
void signal()    
{        
    std::lock_guard<std::mutex> lk(m_);

    //if (count_ < count_max)  // I mark here
    //{  // I mark here
            ++count_; 
            cv_.notify_one();        
    //}    // I mark here
}     
// Lockable requirements    
void lock() { wait(); }    
bool try_lock() { return try_wait(); }    
void unlock() { signal(); } 
private:    
    static const int count_max = 1;    
    int count_;    
    std::mutex m_;    
    std::condition_variable cv_;
}; 

and my source :

#define LOOPCNT 10
atomic<int>  ProcessRow  ;

void f4()
{
    for(int i=0;i<LOOPCNT;i++)
    {
        sem2.unlock() ;
        ++ProcessRow ;
    }
    cout << "i am out" << endl ;
}

void f5()
{
    int icnt = 0 ;
    std::chrono::milliseconds sleepDuration(1000);
    while(1)
    {
        sem2.lock() ;
        ++icnt ;
        std::this_thread::sleep_for(sleepDuration);
        cout << ProcessRow << "in f5 " << endl ;
        --ProcessRow ;
        if(icnt >= LOOPCNT)
            break ;
     }
     printf("(%d)\n",icnt) ;
}

The output :

i am out
10in f5
9in f5
8in f5
7in f5
6in f5
5in f5
4in f5
3in f5
2in f5
1in f5
(10)

Look like signal only effect if the pthread_cond_wait is waiting!! if not , signal is losted !!

And for std::condition_variable , look like std::condition_variable.wait() will wake up the times notify_one() are called ,if you call notify_one() 10 seconds ago and then call wait() , std::condition_variable.wait() still will get that notify_one() message , quite different with pthread_cond_t !!

Am I miss something in this test ? Or just like my test , std::condition and pthread_cond_t just act like the test showes ?

Edit :

I think the following will showes more easier for this test , sorry to forget to unlock so that the test failed , they are the same behavior !!!!

int main()
{
    //pthread_mutex_lock(&mutex);
    ++ProcessRow ;
    pthread_cond_signal(&condA);
    //pthread_mutex_unlock(&mutex);
    printf("sending signal...\n") ;
    sleep(10) ;

    pthread_mutex_lock(&mutex);
    while (ProcessRow <= 0)
        pthread_cond_wait(&condA, &mutex);
    pthread_mutex_unlock(&mutex);
    printf("wait pass through\n") ;
}

This will showes :

sending signal...
wait pass through

And for std::condition_variable

int main()
{
sem2.unlock() ;
std::chrono::milliseconds sleepDuration(10000);
cout << "going sleep" << endl ;
std::this_thread::sleep_for(sleepDuration);
sem2.lock() ;
cout << "lock pass through " << endl ;

} 

Will showes :

going sleep
lock pass through

So it is my fault to do the test wrong , cause to deadlock !!! Thanks for all great advice!

barfatchen
  • 1,630
  • 2
  • 24
  • 48
  • I'm not sure I understand your question. All of your code does exactly what one would expect it to do. If you think signals can get lost, then you fundamentally don't understand that condition variables are stateless. – David Schwartz Aug 14 '13 at 02:27
  • 1
    Your pthread consumer never calls `pthread_mutex_unlock`, but does repeatedly call `pthread_mutex_lock` on the same mutex causing deadlock. – Casey Aug 14 '13 at 02:36
  • Thanks , David , my question is , if I signal to a pthread_cond_t and it is not waiting , this signal get lost , but if I notify_one() to a std::condition_variable and the same it is not waiting , but 10 seconds later , the code std::condition_variable.wait() still get that notify_one() 10 seconds ago , that is my concern , they are so different , Am I doing the right in this test ? – barfatchen Aug 14 '13 at 02:42
  • Thank you , Casey .... You are absolutely right !! How can I forget the unlock statement ? ... that is so careless ~!!! – barfatchen Aug 14 '13 at 02:47
  • @barfatchen Both behaviors are consistent with the defined semantics of a condition variable. A "lost signal" is never a problem because if no threads are waiting, no threads need to be woken up. A spurious wakeup is never a problem because you are required to test the condition after a wakeup. – David Schwartz Aug 14 '13 at 04:05
  • @David ,Thank you , now I got the point of stateless , thanks for your kind explanation !! – barfatchen Aug 14 '13 at 05:48

2 Answers2

3

Both pthread_cond_t and std::condition_variable work the same way. They are stateless and a signal can only get "lost" if no thread is blocked, in which case no signal is needed because there is no thread that needs one.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • Yes , I am doing wrong with this test , if I modify pthread_cond_t to be 2 number : 0 and 1 only between 2 threads , then this test will showes stateless easily , thanks for your advice !! – barfatchen Aug 14 '13 at 04:25
3

In your pthread code, you never unlock the mutex, The consumer() function deadlocks on the second iteration. Also, the outer while loop should break out when some condition is satisfied. I suggest that it should break out when icnt reaches the LOOPCNT. This sort of matches how you break the loop in f5().

void *consumer(void *x)
{
    int icnt = 0 ;
    while(1)
    {
        pthread_mutex_lock(&mutex);
        while (ProcessRow <= 0)
            pthread_cond_wait(&condA, &mutex);
        __sync_sub_and_fetch(&ProcessRow,1) ;
        ++icnt ;
        printf("receving=(%d) icnt=(%d)\n",ProcessRow, icnt) ;
        pthread_mutex_unlock(&mutex);
        if (icnt == LOOPCNT) break;
        usleep(10000) ;
    }
    printf("(%d)\n",ProcessRow) ;
}

It doesn't seem like your std::thread version of the code closely matches the pthread version at all, so I don't think you can compare their executions in this way. Instead of mimicking a semaphore, I think it better to just use the std::condition_variable exactly like you use it in the pthread version of the code. This way, you can really compare apples to apples.

std::condition_variable condA;
std::mutex mutex;
volatile int ProcessRow = 0 ;
#define LOOPCNT 10

void producer()
{
    int idx ;
    for(idx=0;idx<LOOPCNT;idx++)
    {
        std::unique_lock<std::mutex> lock(mutex);
        __sync_add_and_fetch(&ProcessRow,1) ;
        condA.notify_one();
        printf("sending signal...(%d)\n",ProcessRow) ;
    }
    printf("I am out ... \n") ;
}

void consumer()
{
    int icnt = 0 ;
    while(icnt < LOOPCNT)
    {
        if(icnt > 0) usleep(10000);
        std::unique_lock<std::mutex> lock(mutex);
        while (ProcessRow <= 0)
            condA.wait(lock);
        __sync_sub_and_fetch(&ProcessRow,1) ;
        ++icnt ;
        printf("receving=(%d) icnt=(%d)\n",ProcessRow, icnt) ;
    }
    printf("(%d)\n",ProcessRow) ;
}
jxh
  • 69,070
  • 8
  • 110
  • 193
  • `condition_variable::wait()` takes `unique_lock` as argument, not `mutex` directly. – DanielKO Aug 14 '13 at 02:49
  • Thanks , it is really my fault ,forget to add add unlock in pthread_cond_t test , that is very careless , sorry !! – barfatchen Aug 14 '13 at 02:56
  • @jxh Still missed the `wait()`, it takes in the `lock` variable, not the `mutex` variable. – DanielKO Aug 14 '13 at 02:59
  • @DanielKO: Doh, thanks. My brain is clearly not a very thorough C++ syntax checker. – jxh Aug 14 '13 at 03:01
  • As an exercise for the reader, replace the `volatile` variable by an `atomic`; and write an `using namespace std`, no need to make a didactic example more verbose than needed. – DanielKO Aug 14 '13 at 03:07