Pthread conditional signal - not working as expect

2019-04-08 11:35发布

问题:

I am working on a project and trying to use pthread_cond_wait() and pthread_cond_signal() to synchronize two threads.

My code looks something like this:

pthread_mutex_t lock_it = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t write_it = PTHREAD_COND_INITIALIZER;

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

     pthread_t t_send_segments, t_recv_acks;

     pthread_create(&t_send_segments, NULL, send_segments, (void*)NULL);
     pthread_create(&t_recv_acks,     NULL, recv_acks,     (void*)NULL);

     pthread_join(t_recv_acks, (void**)NULL);

     pthread_mutex_destroy(&lock_it);
     pthread_cond_destroy(&write_it);
}

 void* send_segments(void *v) {
    for(;;) {
       pthread_mutex_lock(&lock_it);
       printf("s1\n");
       printf("s2\n");
       pthread_cond_wait(&write_it, &lock_it);
       printf("s3\n");
       printf("s4\n");
       printf("s5\n"); 
       pthread_mutex_unlock(&lock_it);
    }
    return 0;
 }

 void* recv_acks(void *v) {
    for(;;) {
       pthread_mutex_lock(&lock_it);
       printf("r1\n");
       pthread_cond_signal(&write_it);
       printf("r2\n");
       pthread_mutex_unlock(&lock_it);
    }  
    return 0;
 }

The expected output is:

s1
s2
r1
s3
s4
s5
s1
s2
r2
r1
s3
s4
s5

(etc)

My output doesn't follow this pattern at all. Clearly I have a logic error somewhere, but I'm not understanding where. Why doesn't the recv_acks() thread always yield when it hits the pthread_cond_signal() - since the pthread_cond_wait() always executes first (because of the order in which I create the threads) and the cond_wait() always executes since its in the critical section?

回答1:

The pthread_cond_signal function does not cause the current thread to yield and does not release the mutex. All it does is restart one thread that has suspended itself on the condition via pthread_cond_wait. This just means the awakened thread is available for scheduling, it doesn't cause it to execute immediately. The thread-scheduler will schedule it sometime in the future.

Also, just because the s-thread has been awakened and is contending for the mutex, that doesn't mean it's going to get the mutex next. Mutexes are not necessarily fair to all threads that have requested it. According to the pthread_mutex man page: "pthread_mutex_lock locks the given mutex. If the mutex is currently unlocked, it becomes locked and owned by the calling thread, and pthread_mutex_lock returns immediately." So the r-thread can spin in its loop several times, happily unlocking and relocking the mutex several times before being swapped out by the scheduler. This means the s-thread will only get a chance at the mutex if the scheduler happens to interrupt the r-thread during the brief time in which it has released the mutex.

To achieve the output you want, both threads will need to control their execution with a condition and signal each other before suspending themselves. However, this may or may not be what you actually want to do with your real project.

Another note: it doesn't really matter what order you created the threads in. Creating a thread does not yield the creating thread. So the main thread will probably create both threads before either gets scheduled, and the thread scheduler is free to schedule either one of them for execution next. If the s-thread does run first on your platform, that just happens to be the implementation behavior on your platform and is not something that should be relied on.



回答2:

I seem to remember reading somewhere that pthread_cond_signal() doesn't actually cause the thread to yield immediately. Since pthread_cond_signal() doesn't release the lock, the thread that calls it has to continue executing until it does release the lock, and only then does it yield and allow the signaled thread to return from pthread_cond_wait().

If that's the case, then I think your output would look like

s1
s2
r1
r2
s3
s4
s5

etc.... if it's not that, you might need to edit the actual output into your question to get an accurate answer.



回答3:

You are doing a wrong use of conditions. I did not understood exactly what you want but what you are trying to do looks a lot like a typical synchronization problem called the producer/consumer problem which can be implemented with conditions like I did after.

You should have a look on this to understand how conditions are used. The problem is the following : I have two threads, one writing data into a buffer of fixed size and another reading data from the buffer. The first thread cannot write more data if the buffer is full, the second cannot read if the buffer is empty.

#include <stdlib.h>
#include <stdio.h>

#include <pthread.h>

#define BUFFER_SIZE 4 

int buffer_nb_entries = 0; 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 

void send(){
for(;;){
  pthread_mutex_lock(&mutex);
  while( buffer_nb_entries == BUFFER_SIZE) /* If buffer is full, then wait */
    pthread_cond_wait(&cond, &mutex); 

  /* Here I am sure that buffer is not full */
  printf("sending\n"); 
  buffer_nb_entries++;

  pthread_cond_signal(&cond); // signal that the condition has changed. 
  pthread_mutex_unlock(&mutex); 
 }
}

void receive(){
  for(;;){
    pthread_mutex_lock(&mutex); 
    while(buffer_nb_entries == 0)
      pthread_cond_wait(&cond, &mutex);
    /* Here I am sure that buffer is not empty */
    printf("receiving\n");
    buffer_nb_entries--; 
    pthread_cond_signal(&cond); 
    pthread_mutex_unlock(&mutex); 
  }

}

int main(){
  pthread_t s, r; 

  pthread_create(&s, NULL, (void *(*)(void*))send, NULL); 
  pthread_create(&r, NULL, (void *(*)(void*))receive, NULL); 

  pthread_join(s, NULL); 

}

Note that you must test for something before calling pthread_cond_wait(), if you don't and if you signal function has been called before, then you may sleep forever.



回答4:

I think your trouble is coming from trying to use one lock for too many things. You should only use a lock for 1 thing, that way there is no confusion about what you are waiting for. I suggest adding a second lock for the write signal. Also, you should add a second cond_wait for the second grouping of messages. If you don't, the order that things run in will be random. Here is my edited version of your program:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NUM_MESSAGES 3
int messages = 0;
void * send_segments(void *);
void * recv_acks(void *v);
pthread_mutex_t lock_it = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t write_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t write_cond = PTHREAD_COND_INITIALIZER;

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

     pthread_t t_send_segments, t_recv_acks;

     pthread_create(&t_send_segments, NULL, send_segments, (void*)NULL);
     pthread_create(&t_recv_acks,     NULL, recv_acks,     (void*)NULL);

     pthread_join(t_recv_acks, (void**)NULL);
     pthread_join(t_send_segments, (void**)NULL);

     //pthread_mutex_destroy(&lock_it);
     //pthread_cond_destroy(&write_it);
}

 void* send_segments(void *v) {
    for(;;) {
       pthread_mutex_lock(&lock_it);
       printf("s1\n");
       printf("s2\n");
       pthread_mutex_unlock(&lock_it);

       // wait for other thread
       pthread_mutex_lock(&write_mutex);
       pthread_cond_wait(&write_cond, &write_mutex);
       pthread_mutex_unlock(&write_mutex);

       pthread_mutex_lock(&lock_it);
       printf("s3\n");
       printf("s4\n");
       printf("s5\n"); 
       pthread_mutex_unlock(&lock_it);

       // wait for other thread
       pthread_mutex_lock(&write_mutex);
       pthread_cond_wait(&write_cond, &write_mutex);
       pthread_mutex_unlock(&write_mutex);

       if (messages > NUM_MESSAGES) return( NULL );
    }
    return 0;
 }


 void* recv_acks(void *v) {
    for(;;) {
       // write first response
       pthread_mutex_lock(&lock_it);
       printf("r1\n");
       pthread_mutex_unlock(&lock_it);

       // signal other thread
       pthread_mutex_lock(&write_mutex);
       pthread_cond_signal(&write_cond);
       pthread_mutex_unlock(&write_mutex);

       // write second response
       pthread_mutex_lock(&lock_it);
       printf("r2\n\n");
       // increment count before releasing lock, otherwise the other thread
       // will be stuck waiting for a write_cond signal
       messages++;
       pthread_mutex_unlock(&lock_it);

       // signal other thread
       pthread_mutex_lock(&write_mutex);
       pthread_cond_signal(&write_cond);
       pthread_mutex_unlock(&write_mutex);

       if (messages > NUM_MESSAGES) return(NULL);
    } 
    return 0;
 }

If I run this program, I get the following output (note that I added a second newline after r2 for clarity):

leif@indurain:~/tmp$ ./test
s1
s2
r1
s3
s4
s5
r2

s1
s2
r1
s3
s4
s5
r2

s1
s2
r1
s3
s4
s5
r2

s1
s2
r1
s3
s4
s5
r2

leif@indurain:~/tmp$