Get stuck executing multithreads (pthread on Windo

2019-08-02 22:21发布

问题:

I hope you will be able to help me in my trouble. My program is doing something I don't really understand. The program purpose is given as below: Create two threads (task_timer and task_read). These threads have to display on the standard output (stdout) the following message : "Tache 1 Tache 2 Tache 1 Tache 2 ..."

Code :

static void* task_timer(void* arg);
static void* task_read(void* p);
struct strShared{
    pthread_mutex_t mut;
    pthread_mutex_t mut2;
    pthread_cond_t synchro;
    pthread_cond_t synchro2;
};


struct strTimer{
    int wt;
    strShared* psh;
};

static void* task_timer(void* p){
    time_t echeance;
    strTimer* timer;
    int time_waiting = 1000;
    time_t  now;
    if(p != NULL){
        timer = p;
        time_waiting = timer->wt; //ms
        echeance = time (NULL) + TIME_OF_THREAD;

        while (1)
        {
            pthread_mutex_lock(&timer->psh->mut);
            printf("Tache 1\n");
            pthread_cond_signal(&timer->psh->synchro);
            pthread_cond_wait(&timer->psh->synchro2, &timer->psh->mut);
            pthread_mutex_unlock(&timer->psh->mut);
        }
    }
    return NULL;
}

static void* task_read(void* p){
    strTimer* timer;
    if(p != NULL){
        timer = p;
        while(1){
            pthread_mutex_lock(&timer->psh->mut);
            pthread_cond_wait(&timer->psh->synchro, &timer->psh->mut);
            printf("Tache 2\n");
            pthread_cond_signal(&timer->psh->synchro2);
            pthread_mutex_unlock(&timer->psh->mut);
        }

    }
    return NULL;
}
int main (void)
{

    pthread_t ttimer;
    pthread_t tread;

    /* TIMER */
    strTimer timer;
    strShared shtimer;
    shtimer.mut = PTHREAD_MUTEX_INITIALIZER;
    shtimer.mut2 = PTHREAD_MUTEX_INITIALIZER;
    shtimer.synchro = PTHREAD_COND_INITIALIZER;
    shtimer.synchro2 = PTHREAD_COND_INITIALIZER;
    timer.psh = &shtimer;
    timer.wt = 1000;

    /* Threads */
    pthread_create(&ttimer, NULL, task_timer, &timer);
    pthread_create(&tread, NULL, task_read, &timer);

   pthread_join(ttimer,NULL);
   pthread_join(tread,NULL);
   return 0;
}

According to me, this code is the good way to achieve this aim. However, it is not working then I guess I did some mistakes. According to me it is working as below:

  1. Both threads are created and executed in paralelism
  2. Task_read take the mutex mut, wait for the signal synchro and free the mutex because the signal is never arrived
  3. Task_timer take the mutex mut and display "Tache 1" on the standard output
  4. Then, Task_timer sends the signal synchro and waits for the signal synchro2 (free the mutex because the signal is never arrived)
  5. Task_read receives the signal synchro and take the mutex mut and displays "Tache 2"
  6. Task_read sends the signal synchro2 and free the mutex mut and go to the begining of the While loop
  7. Task_timer receives the signal synchro2 and free the mutex and go to the begining of the While loop

However, this is not happened like that. Actually, it seems the program gets stuck after displaying "Tache 1". Someone could explain me why this happens please ? I guess I think bad but I would like to understand ...

Many thanks, esc39

回答1:

If you are new to multi threading I suggest not using condition variables. You do not need to use any condition variable for the objective you described. So remove pthread_cond_wait and pthread_cond_signal lines in both threads.

Instead you can simply add the sleep function after unlocking the mutex in each thread. For e.g. task_read can be modified to:

pthread_mutex_lock(&timer->psh->mut);    
printf("Tache 2\n");
pthread_mutex_unlock(&timer->psh->mut);
usleep(10000);


回答2:

Let's see what happens when task_timer acquire lock first.

pthread_mutex_lock(&timer->psh->mut);      /*acquires a lock*/                                     /*|*/
printf("Tache 1\n");                       /*output*/                                              /*|*/
pthread_cond_signal(&timer->psh->synchro); /*sends a signal*/                                      /*|*/ /* no one is waiting for the signal on this cond, so the signal is ignored*/
pthread_cond_wait(&timer->psh->synchro2, &timer->psh->mut); /*releases lock and waits for signal*/ /*|*/ pthread_mutex_lock(&timer->psh->mut); /*acquires a lock*/
                                                                                                   /*|*/ pthread_cond_wait(&timer->psh->synchro, &timer->psh->mut); /*releases lock and waits for signal*/
                                                                                                   /*|*/
                                                                                                   /*|*/
                                                                                                   /*|*/
                                                                                                   /*|*/ printf("Tache 2\n"); /*never happens*/
pthread_mutex_unlock(&timer->psh->mut);    /*never happens*/                                       /*|*/ pthread_cond_signal(&timer->psh->synchro2);
                                                                                                   /*|*/ pthread_mutex_unlock(&timer->psh->mut);

Deadlock.

Simple recipe: put your pthread_cond_signal() calls outside the critical sections. Consider that as a rule of thumb. When you have a couple or more of threads synchronising with the same set of mutex/cond I can hardly imagine a scenario when it is reasonable to signal from critical section. Semantic of signal and broadcast is like: hey, guys, I'm done with my work on the critical resources, you may follow right on. When the thread is inside critical section, by signalling the cond it makes a false statement. Because it is not done.

BTW, in your case you need an additional flag telling which thread should run. And call the pthread_cond_wait() only in case the flag tells is the other thread's turn.

So, the basic algorithm for each thread would be (in pseudocode):

while(loop_again) {
  do_processing_on_non_critical_resources(); /* it's optional */
  lock(mutex);
  while(not_my_turn) { /* explained later */
    wait(cond,mutex);
  }
  do_processsing_on_critical_resources();
  set_flag_to_other_thread();
  unlock(mutex);
  signal(cond);
  do_processing_on_non_critical_resources(); /* it's optional */
}

The check for not_my_turn is in while loop instead of simple if check, because, according to the documentation, there could be spurious wakeup from the pthread_cond_timedwait() or pthread_cond_wait():

When using condition variables there is always a Boolean predicate involving shared variables associated with each condition wait that is true if the thread should proceed. Spurious wakeups from the pthread_cond_timedwait() or pthread_cond_wait() functions may occur. Since the return from pthread_cond_timedwait() or pthread_cond_wait() does not imply anything about the value of this predicate, the predicate should be re-evaluated upon such return.

So, above you have a general case of synchronised thread. However for your case the answer from M.KHd is correct and sufficient.