【问题标题】:How does the pthread_cond_timedwait() works?pthread_cond_timedwait() 是如何工作的?
【发布时间】:2020-03-25 12:32:45
【问题描述】:

所以我试图了解 pthread_cond_timedwait() 是如何工作的,因为在我的项目上进行同步时遇到了一些问题。这是我想出的代码,但它并没有像我想象的那样工作。我的目标是打印时间,等待 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));                                  
}

【问题讨论】:

  • 您好,欢迎来到 SO!第一:timespec的其他成员不应该被初始化吗?我不知道如果tv_nsec 包含超出范围的内容会发生什么。第二:您预计会发生什么,正在发生什么
  • pthread_cond_timedwait() 的调用的返回值传达了有关所发生事件的有用且相关的信息,但所显示的程序忽略了它。

标签: c multithreading pthreads mutex thread-synchronization


【解决方案1】:

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

您的其他答案讨论了您未能初始化结构t 的某些成员的事实,这确实是一个重大缺陷。当我修改您的程序以捕获pthread_cond_timedwait 调用的返回值时,我发现它返回了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);

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

此外,您不会始终如一地检查函数调用的返回值,正如我在 cmets 中观察到的那样。如果您不关心调用的成功或效果,或者至少可以合理地忽略可能发生的任何失败,但是您依赖于特定结果来执行程序的后续行为,则可以省略此类检查正确地说,检查返回值是安全编程的基本问题。

最后,作为一个小问题,GCC 和 GLibc 属性对 _OPEN_THREADS feature-test 宏没有任何意义,它不是由 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);
}

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

【讨论】:

    【解决方案2】:

    如果您看到的是等待或多或少立即完成,而不是按要求等待 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)

    EDITETIMEDOUT添加了一个测试

    【讨论】:

    • 我用 t.tv_nsec = 0 运行了你的代码,结果是:“pthread_cond_timeout failed: Invalid argument”。
    • 嗯。再进一步看,似乎 time 参数不是唯一未初始化的参与者:mutex 参数也没有初始化,所以也许在我的系统上它碰巧充满了有用的垃圾,而在你的系统上它是无用的垃圾。我已经更新了上面的代码来初始化互斥体,并为返回值添加了另一个测试来检测ETIMEDOUT 作为非错误返回。
    猜你喜欢
    • 2010-12-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-24
    • 2014-02-13
    • 2010-10-14
    • 2013-04-07
    • 2017-07-24
    • 2016-11-13
    相关资源
    最近更新 更多