所以我试图了解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));
}
答案 0 :(得分:1)
如果您看到的是等待或多或少立即完成,而不是按照请求等待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
添加了特定的错误检查(对@JohnBollinger带有h / t)
编辑为ETIMEDOUT
添加了一个测试
答案 1 :(得分:1)
您的代码有几个问题,最重要的都是与使用值不确定的变量有关。
您的其他答案讨论了以下事实:您无法初始化结构t
的某些成员,这确实是一个重大缺陷。当我修改程序以捕获pthread_cond_timedwait
调用的返回值时,我发现它返回EINVAL
,表明参数无效。解决此问题有两种不错的选择:
使用初始化程序声明该变量:
struct timespec t = { 0 };
// ...
t.tv_sec = T + 2;
该特定的初始化程序将第一个成员显式设置为零,所有其他成员隐式设置为默认(零)值-初始化的特性不适用于逐个成员分配。这不仅比显式地分配每个成员更方便,而且还可以照顾您的特定实现版本的结构类型可能具有的所有未记录成员。
OR通过用于此目的的功能为(整个)结构设置一个值。例如,
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);
(注意:条件变量也有一个静态初始化器。)
此外,正如我在注释中观察到的那样,您不能始终如一地检查函数调用的返回值。如果您不在乎调用的成功或效果,或者至少可以合理地忽略可能发生的任何失败,但是在某些情况下取决于程序的后续行为,则可以忽略此类检查正确的说,检查返回值是安全编程的本质。
最后,作为次要问题,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);
}
最后,我观察到在我自己的编程中,我通常定义一个或多个宏来支持返回值检查。我发现使用这样的宏而不是为每个检查显式编写代码可以使程序的整体流程更清晰,更不用说节省我的按键了。我没有在上面说明这一点,尽管即使在如此短的程序中这种方法的用处也可能已经很明显。