Pthread条件信号-未按预期工作

5
我正在处理一个项目,尝试使用 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()总是执行,因为它在临界区内?


你的输出是什么样子? - Hasturkun
“总是首先执行(因为我创建线程的顺序)” - 依赖这一点可能非常危险。在启动线程之前获取互斥锁,并将所有权传递给发送方。 - Dipstick
4个回答

8

pthread_cond_signal函数不会使当前线程放弃,也不会释放互斥锁。它只是重新启动通过pthread_cond_wait在条件上挂起的一个线程。这只意味着唤醒的线程可以进行调度,但并不会立即执行。线程调度程序将在未来的某个时间安排它。

此外,仅仅因为s线程已经被唤醒并正在竞争互斥锁,并不意味着它会立即获得互斥锁。互斥锁不一定对所有请求它的线程都公平。根据pthread_mutex手册的描述:“pthread_mutex_lock锁定给定的互斥锁。如果互斥锁当前未锁定,则变为锁定状态,并由调用线程拥有,pthread_mutex_lock立即返回。” 因此,在被调度程序换出之前,r线程可能会在其循环中旋转几次,愉快地多次解锁和重新锁定互斥锁。这意味着只有当调度程序在r线程在短时间内释放互斥锁时恰好中断它时,s线程才有机会获得互斥锁。

要实现您想要的输出,两个线程都需要使用条件来控制执行,并在挂起自己之前相互发出信号。但是,这可能或可能不是您在实际项目中要做的事情。

另一个注意点:创建线程的顺序并不重要。创建线程不会使创建线程放弃。因此,在任何一个被调度之前,主线程可能会创建两个线程,线程调度程序可以自由地将它们中的任何一个安排为下一个执行的线程。如果s线程在您的平台上首先运行,那只是您平台上的实现行为,不能依赖它。


6
我记得在某个地方读到过,pthread_cond_signal() 实际上并不会立即导致线程放弃执行。由于 pthread_cond_signal() 不会释放锁,因此调用它的线程必须继续执行,直到它释放锁,然后才会放弃执行并允许被通知的线程从 pthread_cond_wait() 返回。
如果是这种情况,那么我认为你的输出将会是:
s1
s2
r1
r2
s3
s4
s5

等等……如果不是那个问题,你可能需要编辑实际输出到你的问题中以获得准确的答案。

4
您正在错误地使用条件语句。我不确定您想要的确切内容,但您尝试做的看起来非常像一个典型的同步问题,称为生产者/消费者问题,可以使用条件语句实现,就像我之前所做的那样。
您应该查看此内容以了解如何使用条件语句。 问题如下: 我有两个线程,一个将数据写入固定大小的缓冲区,另一个从缓冲区读取数据。如果缓冲区已满,则第一个线程无法再写入更多数据;如果缓冲区为空,则第二个线程无法读取。
#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

我认为你的问题来自于试图为太多的事情使用一个锁。你应该只为一个事情使用一个锁,这样就不会有关于你在等待什么的混淆。我建议为写信号添加第二个锁。此外,你应该为第二组消息添加第二个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$ 

我运行了您发布的代码,与您的结果不同。似乎我的接收线程被首先调度,程序就一直卡住了: [ubuntu@apollo:〜/hp_threads] $ gcc -lpthread main.c && ./a.out r1 r2r1 r2r1 r2r1 r2s1 s2 ^C - RishiD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接