【问题标题】:Lost wakeups in pthreadspthread 中的唤醒丢失
【发布时间】:2014-03-15 17:48:01
【问题描述】:

我写了一些程序来尝试 pthread 条件等待。 但问题是不能保证发出信号时会被捕获,从而导致线程失去唤醒。我该如何解决这个问题?

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

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *thread_func1(void* arg){
  printf("thread1 started\n");
  pthread_mutex_lock(&mutex);
  printf("thread1: signalling\n");
  pthread_cond_signal(&cond);
  printf("thread1: signalled\n");
  pthread_mutex_unlock(&mutex);
  printf("thread1: exiting\n");
  pthread_exit(0);
}

void *thread_func2(void* arg){
  printf("thread2 started\n");
  pthread_mutex_lock(&mutex);
  printf("thread2: waiting for signal..\n");
  pthread_cond_wait(&cond, &mutex);
  printf("thread2: signal received\n");
  pthread_mutex_unlock(&mutex);
  printf("thread2: exiting\n");
  pthread_exit(0);
}

int main(int argc, char** argv){
  pthread_t thread1, thread2;

  pthread_create(&thread1, NULL, thread_func1, NULL);
  pthread_create(&thread2, NULL, thread_func2, NULL);

  pthread_join(thread1, 0);
  pthread_join(thread2, 0);

  return 0;
}

这是运行的输出:

thread1 started
thread1: signalling
thread2 started
thread2: waiting for signal..
thread1: signalled
thread1: exiting
// nothing happens now; where is the signal??

这是另一个(有效):

thread2 started
thread2: waiting for signal..
thread1 started
thread1: signalling
thread1: signalled
thread1: exiting
thread2: signal received
thread2: exiting
// program successfully exits

我现在不关心任何类型的临界区,所以我没有使用任何锁。

我如何确保这个东西每次运行都有效?

编辑:我已经按照下面 alk 的回答编辑了代码。我添加了初始化程序和锁。我发布的原始代码是here

【问题讨论】:

    标签: c pthreads


    【解决方案1】:

    正如您所注意到的,线程 1 可能会在线程 2 调用 pthread_cond_wait() 之前发出条件变量的信号。条件变量不会“记住”它已发出信号,因此唤醒将丢失。因此,您需要使用某种变量来确定线程 2 是否需要等待。

    int signalled = 0;
    
    void *thread_func1(void* arg){
      printf("thread1 started\n");
      pthread_mutex_lock(&mutex);
      printf("thread1: signalling\n");
      signalled = 1;
      pthread_cond_signal(&cond);
      printf("thread1: signalled\n");
      pthread_mutex_unlock(&mutex);
      printf("thread1: exiting\n");
      pthread_exit(0);
    }
    
    void *thread_func2(void* arg){
      printf("thread2 started\n");
      pthread_mutex_lock(&mutex);
      printf("thread2: waiting for signal..\n");
      if(!signalled) {
        pthread_cond_wait(&cond, &mutex);
      }
      printf("thread2: signal received\n");
      pthread_mutex_unlock(&mutex);
      printf("thread2: exiting\n");
      pthread_exit(0);
    }
    

    但是,此代码仍然不正确。 pthreads 规范指出条件变量上可能会发生“虚假唤醒”。这意味着即使没有人调用pthread_cond_signal()pthread_cond_broadcast()pthread_cond_wait() 也可能返回。因此,您需要在循环中检查标志,而不是只检查一次:

    void *thread_func2(void* arg){
      printf("thread2 started\n");
      pthread_mutex_lock(&mutex);
      printf("thread2: waiting for signal..\n");
      while(!signalled) {
        pthread_cond_wait(&cond, &mutex);
      }
      printf("thread2: signal received\n");
      pthread_mutex_unlock(&mutex);
      printf("thread2: exiting\n");
      pthread_exit(0);
    }
    

    更新:结合条件变量和计数器功能的另一种方法是使用信号量。

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    sem_t sem;
    
    void *thread_func1(void* arg){
      printf("thread1 started\n");
      printf("thread1: signalling\n");
      sem_post(&sem);
      printf("thread1: signalled\n");
      printf("thread1: exiting\n");
      pthread_exit(0);
    }
    
    void *thread_func2(void* arg){
      printf("thread2 started\n");
      printf("thread2: waiting for signal..\n");
      sem_wait(&sem);
      printf("thread2: signal received\n");
      printf("thread2: exiting\n");
      pthread_exit(0);
    }
    
    int main(int argc, char** argv){
      pthread_t thread1, thread2;
    
      sem_init(&sem);
    
      pthread_create(&thread1, NULL, thread_func1, NULL);
      pthread_create(&thread2, NULL, thread_func2, NULL);
    
      pthread_join(thread1, 0);
      pthread_join(thread2, 0);
    
      sem_destroy(&sem);
    
      return 0;
    }
    

    【讨论】:

    • 这是一种解决方法。在这种情况下,我什至不需要任何类型的条件变量或互斥体,信号变量就足够了。关键是让等待和信号按预期工作。
    • @me.sid 不,你误会了。如果没有条件变量,这将只是一个“忙循环”,等待线程将浪费处理器时间。此外,这可以推广到更复杂的情况(例如,多个等待线程、更复杂的等待条件等)。
    • @me.sid 另一种方法是使用信号量,我会将其添加到我的答案中。
    • @me.sid 这不是解决方法,这是对 pthread_cond_t 的正确使用。你不能在没有谓词的情况下使用 pthread_cond_t,如果你想放弃 CPU,你确实需要一个条件变量(或信号量等)
    • 如果我可以等待 signaled 变量变为 1,我看不到条件变量的使用。如果 signaled 变为 1,我们可以简单地退出繁忙的等待循环。 pthread_cond_wait 在这里有什么帮助?
    【解决方案2】:

    对于初学者,您的代码未初始化mutexcond

    要这样做,至少要这样做:

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    

    传递给pthread_cond_wait() 的互斥锁也应锁定。当函数返回时,它锁定。


    更新

    您的代码引入了比赛。也就是说:如果线程 1 在线程 2 等待信号之前发出信号,线程 2 将永远等待,这很可能发生在您显示的第一个跟踪中。

    【讨论】:

    • 是的。现在,在初始化并在信号和等待语句周围添加锁定/解锁之后,似乎仍然没有成功。
    • 看看上面编辑过的代码。互斥锁不应该有助于避免竞争吗?
    • @me.sid:在任何情况下线程 1 都没有必要首先锁定互斥锁。仅仅在线程 2 之前启动线程 1 是不够的。
    猜你喜欢
    • 1970-01-01
    • 2012-08-15
    • 2018-04-19
    • 2015-05-28
    • 2022-01-12
    • 1970-01-01
    • 2013-06-04
    • 2018-10-13
    • 2022-11-03
    相关资源
    最近更新 更多