Pthread условный сигнал - не работает, как ожидалось - PullRequest
5 голосов
/ 22 марта 2009

Я работаю над проектом и пытаюсь использовать pthread_cond_wait() и pthread_cond_signal() для синхронизации двух потоков.

Мой код выглядит примерно так:

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;
 }

Ожидаемый результат:

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

(etc)

Мой вывод не соответствует этому шаблону вообще. Очевидно, у меня где-то есть логическая ошибка, но я не понимаю, где. Почему поток recv_acks() не всегда всегда дает, когда он достигает pthread_cond_signal() - поскольку pthread_cond_wait() всегда выполняется первым (из-за порядка, в котором я создаю потоки) и cond_wait() всегда выполняется, так как в критической секции?

Ответы [ 4 ]

8 голосов
/ 22 марта 2009

Функция pthread_cond_signal не приводит к тому, что текущий поток уступает, и не освобождает мьютекс. Все, что он делает, это перезапускает один поток, который приостановил свое состояние с помощью pthread_cond_wait. Это просто означает, что пробужденный поток доступен для планирования, но не вызывает его немедленного выполнения. Планировщик потока запланирует это когда-нибудь в будущем.

Кроме того, только потому, что s-поток был пробужден и борется за мьютекс, это не значит, что он получит мьютекс следующим. Мьютексы не обязательно справедливы для всех потоков, которые его запрашивали. Согласно справочной странице pthread_mutex: «pthread_mutex_lock блокирует данный мьютекс. Если мьютекс в настоящее время разблокирован, он становится заблокированным и принадлежит вызывающему потоку, и pthread_mutex_lock немедленно возвращается». Таким образом, r-поток может вращаться в своем цикле несколько раз, с радостью разблокируя и снова блокируя мьютекс несколько раз, прежде чем он будет заменен планировщиком. Это означает, что s-поток получит шанс на мьютекс, только если планировщик прервет r-поток в течение короткого времени, в течение которого он освободил мьютекс.

Чтобы получить желаемый результат, оба потока должны будут контролировать свое выполнение с условием и сигнализировать друг другу, прежде чем приостановить себя. Тем не менее, это может или не может быть то, что вы на самом деле хотите сделать со своим реальным проектом.

Еще одно замечание: не имеет значения, в каком порядке вы создали потоки. Создание потока не приводит к созданию потока. Таким образом, главный поток, вероятно, создаст оба потока до того, как любой из них будет запланирован, и планировщик потока может свободно планировать любой из них для выполнения следующим. Если s-поток запускается первым на вашей платформе, то это просто поведение реализации на вашей платформе, и на него нельзя полагаться.

6 голосов
/ 22 марта 2009

Кажется, я помню, что где-то читал, что pthread_cond_signal() на самом деле не приводит к немедленному выходу потока. Поскольку pthread_cond_signal() не снимает блокировку, вызывающий его поток должен продолжать выполняться до тех пор, пока не снимет блокировку, и только , тогда выдаст и разрешит сигнальному потоку вернуться из pthread_cond_wait(). .

Если это так, то я думаю, что ваш вывод будет выглядеть как

s1
s2
r1
r2
s3
s4
s5

и т. Д .... если это не так, вам, возможно, потребуется отредактировать фактический вывод в свой вопрос, чтобы получить точный ответ.

4 голосов
/ 22 марта 2009

Вы неправильно используете условия. Я не совсем понял, что вы хотите, но то, что вы пытаетесь сделать, очень похоже на типичную проблему синхронизации, называемую проблемой производителя / потребителя, которая может быть реализована в условиях, аналогичных тем, которые я делал после.

Вам следует взглянуть на это, чтобы понять, как используются условия. Проблема заключается в следующем: У меня есть два потока, один записывает данные в буфер фиксированного размера, а другой читает данные из буфера. Первый поток не может записать больше данных, если буфер заполнен, второй не может прочитать, если буфер пуст.

#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); 

}

Обратите внимание, что вы должны что-то проверить перед вызовом pthread_cond_wait (), если вы этого не сделаете, и если вы сигналили, что функция была вызвана ранее, то вы можете спать вечно.

1 голос
/ 22 марта 2009

Думаю, ваша проблема в том, что вы пытаетесь использовать один замок для слишком многих вещей. Вы должны использовать блокировку только для одной вещи, таким образом, нет никакой путаницы в том, что вы ждете. Я предлагаю добавить второй замок для сигнала записи. Кроме того, вы должны добавить второй cond_wait для второй группы сообщений. Если вы этого не сделаете, порядок запуска будет случайным. Вот моя отредактированная версия вашей программы:

#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;
 }

Если я запускаю эту программу, я получаю следующий вывод (обратите внимание, что я добавил вторую строку после r2 для ясности):

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$ 
...