pthread_cond_timedwait() 是如何工作的?

How does the pthread_cond_timedwait() works?

所以我试图了解 pthread_cond_timedwait() 是如何工作的,因为我在对我的项目进行同步时遇到了一些问题。这是我想出的代码,但它并没有像我想象的那样工作。我的 objective 是打印时间,等待 2 秒,然后再次打印时间以查看时间的流逝。

//gcc -Wall -pthread timedwait.c -o  exe


#define _OPEN_THREADS                                                           
#include <pthread.h>                                                            
#include <stdio.h>                                                              
#include <time.h>                                                               
#include <errno.h>
#include <stdlib.h>




int main() {                                                                        
  pthread_cond_t cond;                                                          
  pthread_mutex_t mutex;                                                        
  time_t T;                                                                     
  struct timespec t;                                                            

  if (pthread_cond_init(&cond, NULL) != 0) {                                    
    perror("pthread_cond_init() error");                                        
    exit(2);                                                                    
  }                                                                             


  time(&T);                                                                     
  t.tv_sec = T + 2;                                                             
  printf("starting timedwait at %s", ctime(&T));                                
  pthread_cond_timedwait(&cond, &mutex, &t);                                                                                               
  time(&T);                                                                     
  printf("timedwait over at %s", ctime(&T));                                  
}

如果您看到等待或多或少立即完成,而不是按要求等待 2 秒,那么这几乎可以肯定是因为 timespec 对象包含垃圾。

当然 tv_sec 包含秒数,但是 tv_nsec 包含纳秒,并且可以很容易地忽略它:我不在乎它是不是几纳秒多于或少于 2 秒。

但事实并非如此。如果堆栈中的垃圾恰好超出范围,它将失败。让我们看看如何故意用垃圾(负数)填充 tv_nsec

// gcc -Wall -pthread timedwait.c -o exe        <-- thank you for this

#define _OPEN_THREADS
#include <pthread.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

int main() {
  pthread_cond_t cond;
  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  time_t T;
  struct timespec t;

  if (pthread_cond_init(&cond, NULL) != 0) {
    perror("pthread_cond_init() error");
    exit(2);
  }

  time(&T);
  t.tv_sec = T + 5;
  t.tv_nsec = -1;  // INTENTIONAL JUNK

  printf("starting timedwait at %s", ctime(&T));
  int err = pthread_cond_timedwait(&cond, &mutex, &t);
  if (err != 0  &&  err != ETIMEDOUT)
  {
    printf("pthread_cond_timeout failed: %s\n", strerror(err));
    exit(EXIT_FAILURE);
  }
  time(&T);
  printf("timedwait over at %s", ctime(&T));

  return 0;
}

当我 运行 这个 - 使用 T + 5 来确定我可以看到等待 - 它立即失败:

$ ./exe
starting timedwait at Fri Nov 29 19:50:52 2019
pthread_cond_timedwait failed: Invalid argument

但是更改为 t.tv_nsec = 0 会使它等待您期望的时间。

编辑pthread_cond_timeout 添加了特定的错误检查(h/t 到@JohnBollinger)

编辑ETIMEDOUT

添加了测试

您的代码有几个问题,最重要的问题都与使用值不确定的变量有关。

您的另一个回答讨论了您未能初始化结构的某些成员这一事实 t,这确实是一个重大缺陷。当我修改您的程序以捕获 pthread_cond_timedwait 调用的 return 值时,我发现它 returns EINVAL,表明参数无效。有两种解决该问题的好方法:

  • 用初始值设定项声明该变量:

    struct timespec t = { 0 };
    // ...
    t.tv_sec = T + 2;
    

    那个特定的初始化程序将第一个成员显式设置为零,所有其他成员都隐式设置为默认(零)值——初始化的一个特征不适用于逐个成员的分配。这不仅比显式分配每个成员更方便,而且还处理了该结构类型的特定实现版本可能具有的任何未记录的成员。

  • 或通过服务于此目的的函数为(整个)结构设置一个值。例如,

    struct timespec t;
    clock_gettime(CLOCK_REALTIME, &t);
    // ... no need for T or time() ...
    t.tv_sec += 2;
    

    这需要一个不依赖于结构原始值的函数(例如clock_gettime)。

更正变量 t 的初始化问题后,生成的程序对我来说仍然失败,但出现不同的错误:EPERM,表明该操作不允许特定组合争论。对于这个特定的函数,那是因为互斥量在调用时没有被调用线程锁定,但实际上更糟糕的是:互斥量甚至没有被初始化。可以通过 pthread_mutex_init 初始化它,但如果您不关心设置任何非默认属性,那么静态初始化程序更方便:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int result;
// ...
result = pthread_mutex_lock(&mutex);

(注意:条件变量也有一个静态初始化器。)

此外,正如我在评论中观察到的那样,您没有始终如一地检查函数调用的 return 值。如果您不关心调用的成功或效果,或者至少忽略可能发生的任何失败是合理的,但程序的后续行为取决于特定结果,则可以省略此类检查正确地说,检查 return 值是安全编程的基本事项。

最后,作为一个小问题,GCC 和 GLibc 认为 _OPEN_THREADS 功能测试宏没有意义,POSIX 也没有定义它。它似乎特定于 IBM 的工具链。它可能对你的情况没有害处,但它绝对不合适且无益,即使在它适用的环境中,似乎也有更好的方法来获得它提供的相同效果。

您程序的这个变体解决了所有这些问题:

// gcc -Wall -pthread timedwait.c -o  exe

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

int main() {
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    struct timespec t;
    int result;

    result = pthread_mutex_lock(&mutex);
    if (result != 0) {
        fprintf(stderr, "pthread_mutex_lock: %s\n", strerror(result));
        exit(EXIT_FAILURE);
    }

    result = clock_gettime(CLOCK_REALTIME, &t);
    if (result == -1) {
        perror("clock_gettime");
        exit(EXIT_FAILURE);
    }

    // Return-value check is non-essential here:
    printf("starting timedwait at %s", ctime(&t.tv_sec));

    t.tv_sec += 2;
    result = pthread_cond_timedwait(&cond, &mutex, &t);
    if (result != ETIMEDOUT) {
        fprintf(stderr, "%s\n", strerror(result));
    }

    result = clock_gettime(CLOCK_REALTIME, &t);
    if (result == -1) {
        perror("clock_gettime");
        exit(EXIT_FAILURE);
    }

    // Return-value check is non-essential here:
    printf("timedwait over at %s", ctime(&t.tv_sec));

    // Return-value check is non-essential here, because we'll just exit anyway:
    pthread_mutex_unlock(&mutex);
}

最后,我发现在我自己的编程中,我通常会定义一个或多个宏来支持 return 值检查。我发现使用这样的宏而不是明确地为每个检查编写代码可以使程序的整体流程更加清晰,更不用说节省我的击键次数了。我没有在上面证明这一点,尽管即使在这么短的程序中,这种方法的用处可能已经很明显了。