0
votes

I have implemented a conditional variable example. However, I see that once I sleep after the signal is received by the pthread_cond_wait. The code after sleep is not executed. It directly jumps to the else condition. I understand that the mutex is unlocked now. I have just two threads A and B. Both the threads have two different startup routine. If I put a sleep in thread A after getting a signal, then it should schedule thread B. Now, once thread B is done. The thread A should resume from the place after sleep. However, it is resuming not from sleep. It re-acquires the lock. See my example, and it's output. You will notice that the following lines never got printed - To quickly, troubleshoot my issue- just look at the following function - void *Thread_Function_A(void *thread_arg) and void *Thread_Function_B(void *thread_arg). Other functions are not that important.

The cond_wait is unblocked now
The thread A proceeds
Thread A unlocked

Here is the program now -

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <iostream>
/** get pid **/
#include <sys/types.h>
#include <unistd.h>
/** kill signal **/
#include <signal.h>

using namespace std;

int shared_variable = 7;

pid_t pid_A;
pid_t pid_B;



class helium_thread
{
  private:
  pthread_t *thread_id;
  pid_t process_pid;

  public:
  static pthread_mutex_t mutex_thread;
  static pthread_cond_t cond_var;
  void set_thread_id(pthread_t tid);
  pthread_t *get_thread_id();
  int create_thread(pthread_t *thread_ptr, const pthread_attr_t *attr, void * (*start_routine)(void *), void *arg );
  helium_thread();  
  ~helium_thread();

};

helium_thread thread_1, thread_2;
/** The definition of the static member can't be inside a function, You need to put it outside **/
/** When I tried using inside a function, I got the error - error: invalid use of qualified-name ‘helium_thread::mutex_thread **/

pthread_mutex_t helium_thread::mutex_thread = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  helium_thread::cond_var = PTHREAD_COND_INITIALIZER;


void helium_thread::set_thread_id( pthread_t tid)
{
   *(this->thread_id) = tid;    
}

pthread_t * helium_thread::get_thread_id( )
{
   return (this->thread_id);
}

int helium_thread::create_thread(pthread_t *thread_ptr, const pthread_attr_t *attr, void * (*start_routine)(void *), void *arg )
{
   int ret;
   ret = pthread_create(thread_ptr,attr,start_routine,(void *)arg)  ;
   cout<<"Thread created "<<std::hex<<thread_ptr<<endl;
   return ret;

}

helium_thread::helium_thread()
{

    thread_id = new pthread_t;
    cout<<"Constructor called "<<std::hex<<thread_id<<endl;
}

helium_thread::~helium_thread()
{
    cout<<"Destructor called"<<std::hex<<thread_id<<endl;
    delete thread_id;
}


/** While defining the methods of the class, Keywords static and virtual should not be repeated in the definition. **/
/** They should only be used in the class declaration. **/

void handler(int sig)
{
    //do nothing
    cout<<"Handler called"<<endl;
}


void *Thread_Function_A(void *thread_arg)
{
  int rc = 0;

  pid_A = getpid();

  cout<<"The pid value of Thread A is"<< pid_A << endl;

  while(1)
  {

    pthread_mutex_lock(&(helium_thread::mutex_thread));
    cout<<"Thread A lock acquire first"<<endl;

   if ( shared_variable  != 5) 
   {
        /** Now you put a sleep to introduce a race condition **/
        /** You will find that there is no race condition here **/
       cout<<"Going to conditional wait"<<endl;
       //cout<<"Sleep thread A"<<endl;
       //sleep(5);   
       pthread_cond_wait(&helium_thread::cond_var, &helium_thread::mutex_thread);
       cout<<"Sleep after cond_wait"<<endl;
       sleep(5);
       cout<<"The cond_wait is unblocked now "<<endl;
       cout<<"The thread A proceeds"<<endl;
       cout<<"The shared_variable value = "<< std::dec<< shared_variable << endl;

       pthread_mutex_unlock(&(helium_thread::mutex_thread));
       cout<<"Thread A unlocked"<<endl;



   }  
   else
   {  
      cout<<"Else condition thread A..shared variable value is "<<shared_variable<<endl;
      cout<<"The condition of thread A is met now"<<endl;
      pthread_mutex_unlock(&(helium_thread::mutex_thread));
      cout<<"Thread A unlocked in else condition"<<endl;
      pthread_exit(NULL);   
   }

 }
}


void *Thread_Function_B(void *thread_arg)
{
  pthread_mutex_lock(&(helium_thread::mutex_thread));   

  pid_B = getpid();

  cout<<"The pid value of Thread B is"<< pid_B << endl;

  shared_variable = 5;
  /** Now you put a sleep to introduce a race condition **/
  /** You will find that there is no race condition here **/
  //sleep(5);

  cout<<"Signal the thread A now "<<endl;

  pthread_cond_signal (&helium_thread::cond_var);

  cout<<"Changed the shared_variable value now"<<endl;



  pthread_mutex_unlock(&(helium_thread::mutex_thread));

  cout<<"Return thread function b now"<<endl; 

}


int main(int argc, char *argv[])
{

   pid_t thread_pid_val = getpid();
   thread_1.create_thread((thread_1.get_thread_id()),NULL,Thread_Function_A,&thread_pid_val);
   thread_2.create_thread((thread_2.get_thread_id()),NULL,Thread_Function_B,&thread_pid_val);
   pthread_join( *(thread_1.get_thread_id()), NULL);
   pthread_join( *(thread_2.get_thread_id()), NULL);

   return  0;   
}

The output is the following.

    $ ./thread_basic.out 
Constructor called 0x2012010
Constructor called 0x2012030
Thread created 0x2012010
The pid value of Thread A is5bfd
Thread created 0x2012030
Thread A lock acquire first
Going to conditional wait
The pid value of Thread B is5bfd
Signal the thread A now 
Changed the shared_variable value now
Return thread function b now
Sleep after cond_wait
The cond_wait is unblocked now 
The thread A proceeds
The shared_variable value = 5
Thread A unlocked
Thread A lock acquire first
Else condition thread A..shared variable value is 5
The condition of thread A is met now
Thread A unlocked in else condition
Destructor called0x2012030
Destructor called0x2012010
2
This is FAR too much code. Where is your testcase?Lightness Races in Orbit
Also, as a hard rule: the correctness of your program should not depend on the speed of the processor (ie: the presence or absence of calls to sleep). This is also true of testing - in industry we have a word for test cases that involve sleeps: "flaky". Prefer something callback driven for testing, or using a mock (google gmock).Mark
Side note: life is easier when you use C++11 threads. The API is designed so that it is harder to write silly bugs. Aside from not having to remember to call the create and destroy functions on each object, you have lock guards to ensure exception-safety, one form of condition_variable::wait is designed to take a lambda so that it is impossible to forget to loop and thereby ensure correctness, among other helpful design-induced safety mechanisms.Mark
Just look at the routine function of thread A and thread B.Other code is not that relevant. I am trying to understand why sleep induces to ignore some of the sequential execution. In fact, some of the instructions never executed as I never got the prints. For simplicity, just look at the following functions - void *Thread_Function_A(void *thread_arg) and void *Thread_Function_B(void *thread_arg). Other code is not that relevant.dexterous
Based on the output, the branch with the condition variable wait and the sleep() is never taken. You might want to output something before waiting on the condition variable.Dietmar Kühl

2 Answers

1
votes

I removed most of code clutter and changed the log statements to be a little clearer. I am not seeing what you are seeing.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

using namespace std;

int shared_variable = 7;
pid_t pid_A;
pid_t pid_B;

class helium_thread
{
    private:
        pthread_t *thread_id;
        pid_t process_pid;

    public:
        static pthread_mutex_t mutex_thread;
        static pthread_cond_t cond_var;
        void set_thread_id(pthread_t tid);
        pthread_t *get_thread_id();
        int create_thread(pthread_t *thread_ptr, const pthread_attr_t *attr,
                          void * (*start_routine)(void *), void *arg );
        helium_thread();
        ~helium_thread();
};

helium_thread thread_1, thread_2;

pthread_mutex_t helium_thread::mutex_thread = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t helium_thread::cond_var = PTHREAD_COND_INITIALIZER;

void helium_thread::set_thread_id( pthread_t tid) {*(this->thread_id) = tid;}

pthread_t * helium_thread::get_thread_id() {return (this->thread_id);}

int helium_thread::create_thread(pthread_t *thread_ptr, const pthread_attr_t *attr, void * (*start_routine)(void *), void *arg )
{
    int ret;
    ret = pthread_create(thread_ptr, attr, start_routine, (void *)arg) ;
    return ret;
}

helium_thread::helium_thread()  {thread_id = new pthread_t; }    
helium_thread::~helium_thread() {delete thread_id;}

void *Thread_Function_A(void *thread_arg)
{
    while (1)
    {
        pthread_mutex_lock(&(helium_thread::mutex_thread));
        cout << "TA lock acquired" << endl;

        if ( shared_variable != 5)
        {
            cout << "TA Going to conditional wait" << endl;
            pthread_cond_wait(&helium_thread::cond_var, &helium_thread::mutex_thread);
            cout << "TA Sleep after cond_wait" << endl;
            sleep(5);
            cout << "TA The cond_wait is unblocked now " << endl;
            cout << "TA The thread A proceeds" << endl;
            cout << "TA The shared_variable value = " << shared_variable << endl;
            pthread_mutex_unlock(&(helium_thread::mutex_thread));
            cout << "TA unlocked" << endl;
        }
        else
        {
            cout << "TA Else condition thread A..shared variable value is " << shared_variable << endl;
            cout << "TA The condition of thread A is met now" << endl;
            pthread_mutex_unlock(&(helium_thread::mutex_thread));
            cout << "TA unlocked in else condition" << endl;
            pthread_exit(NULL);
        }
    }
    return NULL;
}

void *Thread_Function_B(void *thread_arg)
{
    pthread_mutex_lock(&(helium_thread::mutex_thread));
    shared_variable = 5;
    cout << "TB Signal the thread A now " << endl;
    pthread_cond_signal (&helium_thread::cond_var);
    cout << "TB the changed shared_variable is now" << endl;
    pthread_mutex_unlock(&(helium_thread::mutex_thread));
    cout << "TB Return thread now" << endl;

    return NULL;
}

int main(int argc, char *argv[])
{
    thread_1.create_thread((thread_1.get_thread_id()), NULL, Thread_Function_A, NULL);
    thread_2.create_thread((thread_2.get_thread_id()), NULL, Thread_Function_B, NULL);
    pthread_join( *(thread_1.get_thread_id()), NULL);
    pthread_join( *(thread_2.get_thread_id()), NULL);

    return 0;
}

Here are the 3 scenarios I keep seeing played out. None involve jumping code. The sleep may (or may not) introduce some variability in execution but I don't see anything that is unexpected.

One

TA lock acquired
TA Going to conditional wait
TB Signal the thread A now 
TB the changed shared_variable is now
TA Sleep after cond_wait
TB Return thread now
TA The cond_wait is unblocked now 
TA The thread A proceeds
TA The shared_variable value = 5
TA unlocked
TA lock acquired
TA Else condition thread A..shared variable value is 5
TA The condition of thread A is met now
TA unlocked in else condition

Two

TA lock acquired
TA Going to conditional wait
TB Signal the thread A now 
TB the changed shared_variable is now
TB Return thread now
TA Sleep after cond_wait
TA The cond_wait is unblocked now 
TA The thread A proceeds
TA The shared_variable value = 5
TA unlocked
TA lock acquired
TA Else condition thread A..shared variable value is 5
TA The condition of thread A is met now
TA unlocked in else condition

Three

TB Signal the thread A now 
TB the changed shared_variable is now
TB Return thread now
TA lock acquired
TA Else condition thread A..shared variable value is 5
TA The condition of thread A is met now
TA unlocked in else condition
1
votes

There is difference in two functions run in threads Thread_Function_A,Thread_Function_B.

In Thread_Function_B 1st thing you do is acquire lock whereas in Thread_Function_A you 1st get pid and then start a while loop and then acquire lock.

So when 2 threads are created B 1st gets the lock and A has to wait for its chance to get the lock. B sets the value of shared variable as 5 and then signal condition variable. Although in this case there is no thread as of now waiting on condition (as A has yet to acquire lock and then wait on condition).

When B releases lock A acquires it finds the shared variable is 5 and hence goes in else condition.

Ideally you would have wanted A to 1st get the lock and wait on condition , but in your current implementation this is not happening.

Try and 1st acquire lock in A and then proceed.

  void *Thread_Function_A(void *thread_arg)
  {
    pthread_mutex_lock(&(helium_thread::mutex_thread));
    //remaining logic