pthread_cond_signal 不会唤醒等待的线程

时间:2021-05-27 15:47:06

标签: pthreads condition-variable

我正在编写一个程序,它创建一个打印 10 个数字的线程。当它打印 5 个时,它等待并通知主线程,然后它继续接下来的 5 个数字

这是 test.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>

int rem = 10;
int count = 5;
pthread_mutex_t mtx;
pthread_cond_t cond1;
pthread_cond_t cond2;


void *f(void *arg)
{
  int a;
  srand(time(NULL));
  while (rem > 0) {
      a = rand() % 100;
      printf("%d\n",a);
      rem--;
      count--;
      if (count==0) {
          printf("time to wake main thread up\n");
          pthread_cond_signal(&cond1);
          printf("second thread waits\n");
          pthread_cond_wait(&cond2, &mtx);
          printf("second thread woke up\n");
      }
  }
  pthread_exit(NULL);
}



int main()
{
  pthread_mutex_init(&mtx, 0);
  pthread_cond_init(&cond1, 0);
  pthread_cond_init(&cond2, 0);
  pthread_t tids;
  pthread_create(&tids, NULL, f, NULL);
  while(1) {
    if (count != 0) {
      printf("main: waiting\n");
      pthread_cond_wait(&cond1, &mtx);
      printf("5 numbers are printed\n");
      printf("main: waking up\n");
      pthread_cond_signal(&cond2);
      break;
    }
   pthread_cond_signal(&cond2);
   if (rem == 0) break;

 }
 pthread_join(tids, NULL);
} 

程序的输出为:

main: waiting
//5 random numbers
time to wake main thread up
second thread waits
5 numbers are printed
main: waking up

自从我做了pthread_cond_signal(&cond2);我以为线程会唤醒并打印其余的数字,但事实并非如此。任何想法为什么?提前致谢。

1 个答案:

答案 0 :(得分:0)

总结

这些问题已在评论中进行了总结,或者至少是其中的大部分。然而,为了将实际答案记录在案:

关于程序对共享变量和同步对象的使用几乎没有什么是正确的。它的行为是未定义的,观察到的具体表现只是众多可能行为中更可能的表现之一。

访问共享变量

如果两个不同的线程在运行期间访问(读取或写入)同一个非原子对象,并且至少有一次访问是写入,那么所有访问都必须通过同步操作得到适当的保护。

这些有很多种,在 StackOverflow 答案中无法全面涵盖,但其中最常见的是使用互斥锁来保护访问。在这种方法中,在程序中创建了一个互斥锁,并指定用于保护对一个或多个共享变量的访问。每个想要访问这些变量之一的线程在这样做之前都会锁定互斥锁。在稍后的某个时间,线程解锁互斥锁,以免其他线程被永久阻止锁定互斥锁本身。

示例:

pthread_mutex_t mutex;  // must be initialized before use

int shared_variable;

// ...

void *thread_one_function(void *data) {
    int rval;

    // some work ...

    rval = pthread_mutex_lock(&mutex);
    // check for and handle lock failure ...
    
    shared_variable++;
    // ... maybe other work ...

    rval = pthread_mutex_unlock(&mutex);
    // check for and handle unlock failure ...

    // more work ...
}

在你的程序中,remcount 变量都是线程间共享的,对它们的访问需要同步。您已经有了一个互斥锁,使用它来保护对这些变量的访问似乎是合适的。

使用条件变量

条件变量之所以有这个名字,是因为它们旨在支持特定的线程交互模式:只有当某个条件(取决于其他线程执行的操作)时,一个线程才希望继续通过某个点线程,很满意。此类要求经常出现。可以通过繁忙循环来实现这一点,其中线程重复测试条件(使用适当的同步)直到它为真,但这很浪费。条件变量允许这样的线程暂停操作,直到有必要再次检查条件。

条件变量的正确使用模式应被视为对繁忙循环的修改和特化:

  1. 线程锁定一个互斥锁,保护要计算条件的数据;
  2. 线程测试条件;
  3. 如果条件满足,则此过程结束;
  4. 否则,线程等待指定的条件变量,指定(相同的)互斥量;
  5. 当线程在等待后恢复时,它会循环回到 (2)。

示例:

pthread_cond_t cv;  // must be initialized before use

void *thread_two_function(void *data) {
    int rval;

    // some work ...

    rval = pthread_mutex_lock(&mutex);
    // check for and handle lock failure ...

    while (shared_variable < 5) {
        rval = pthread_cond_wait(&cv, &mutex);
        // check for and handle wait failure ...
    }

    // ... maybe other work ...

    rval = pthread_mutex_unlock(&mutex);
    // check for and handle unlock failure ...

    // more work ...
}

注意

  • 过程终止(在(3)),线程仍然保持互斥锁锁定。线程有义务将其解锁,但有时它会希望先执行该互斥锁保护下的其他工作。
  • 当线程在 CV 上等待时,互斥量会自动释放,并在线程从等待中返回之前重新获取。这允许其他线程有机会访问受互斥锁保护的共享变量。
  • 要求调用 pthread_cond_wait() 的线程锁定了指定的互斥锁。否则,调用会引发未定义的行为。
  • 这种模式依赖于线程在适当的时间向 CV 发送信号或广播,以通知任何当时正在等待的其他线程他们可能想要重新评估他们正在等待的条件。上面的示例中并未对此进行建模。
  • 多个 CV 可以使用同一个互斥锁。
  • 同一个简历可以在多个地方使用,并且具有不同的关联条件。当涉及的所有条件都受到其他线程的相同或相关操作的影响时,这样做是有意义的。
  • 条件变量不存储信号。只有已被阻塞等待指定 CV 的线程才会受到 pthread_cond_signal()pthread_cond_broadcast() 调用的影响。

你的程序

您的程序在这方面存在多个问题,其中包括:

  • 两个线程都在不同步的情况下访问共享变量remcount,其中一些访问是写。因此,整个程序的行为是未定义的。常见的表现之一是线程不观察彼此对这些变量的更新,尽管事情似乎按预期工作也是可能的。或其他任何东西。

  • 两个线程都调用 pthread_cond_wait() 而不保持互斥锁锁定。因此,整个程序的行为是未定义的。 “未定义”意味着“未定义”,但 UB 可能表现为,例如,一个或两个线程在 CV 发出信号后未能从它们的等待中返回。

  • 两个线程都没有采用 CV 使用的标准模式。任何一个都没有明确的关联条件,线程肯定不会测试一个。这留下了“此简历已发出信号”的隐含条件,但这是不安全的,因为在等待之前无法对其进行测试。特别是,它留下了这个可能的事件链:

    1. 主线程阻塞在 cond1 等待。
    2. 第二个线程发出 cond1 信号。
    3. 在第二个线程继续等待 cond2 之前,主线程至少通过信号 cond2 一直运行。

    一旦(3)发生,程序就无法避免死锁。主线程中断循环并尝试加入第二个线程,同时第二个线程到达其 pthread_cond_wait() 调用并阻塞等待永远不会到达的信号。

    即使前面几点中提到的问题得到纠正,这一系列事件也可能发生,并且它可以准确地表现出您报告的可观察到的行为。

相关问题