C语言陷阱与技巧第6节,代码封装为函数就不可用了?
发表于: 2019-04-28 08:19:53 | 已被阅读: 31 | 分类于: C语言
在C语言程序开发中,如果某个函数需要阻塞等待某些信息,最好将其加上超时限制,否则该函数可能会“无限等待”,将整个线程卡死,这一点上一节已经通过实例较为详细的讨论。但是C语言并没有提供“超时”语法,需要C语言程序员自己实现一套“超时”机制。
避免“繁琐”的代码,小明的困惑
不过,C语言中的“超时”机制并不难实现,上一节利用 usleep() 函数就建立了一套非常简易的“超时”功能,相关的C语言代码这里再写一次,请看:
long otime = get_cur_ms();
while( !ready && get_cur_ms()-otime < 5000)
usleep(100);
if(get_cur_ms()-otime >= 5000)
printf("time out\n");
// get_cur_ms() 函数可以获取当前时间毫秒数
上述C语言代码将阻塞等待 ready 位,但是并不会无限等待下去,而是最多等待 5000ms(即 5 秒)。这么处理虽然比较粗糙,但是的确能够解决“无限等待”问题,只不过仅仅等待一个 ready 位就需要写 3 行代码,如果需要做“超时”处理的地方比较多,整个C语言代码看起来就显得非常啰嗦了。
要是算上“超时”判断语句 if(get_cur_ms()-otime >= 5000)的话,就需要至少 4 行代码了。
int cond_timeout(int cond, long timeout)
{
long otime = get_cur_ms();
while( !cond && get_cur_ms()-otime < timeout)
usleep(100);
if(get_cur_ms()-otime >= timeout)
return 1;
else
return 0;
}
定义好 cond_timeout() 函数后,小明将上一节等待 ready 位的“超时”C语言代码:
long otime = get_cur_ms();
while( !ready && get_cur_ms()-otime < 5000)
usleep(100);
if(get_cur_ms()-otime >= 5000)
printf("time out\n");
修改为:
if(cond_timeout(ready, 5000))
printf("timeout\n");
# gcc t.c -lpthread
# ./a.out
timeout
exit
奇怪,thread() 线程函数明明在 2 秒后就将 ready 置位了,怎么还是输出了 “time out”呢?小明对此困惑不已。
“小明的困惑”解析
其实小明遇到的问题很像脑筋急转弯,如果读者和小明一样感到困惑,一定是因为“没反应过来”。cond_timeout() 函数没有像小明的预期一样工作的原因很简单:
#define cond_timeout(__cond, __timeout) \
({ \
long __mtime = get_cur_ms(); \
while( !(__cond) ){ \
usleep(200); \
if(get_cur_ms()-__mtime >= __timeout) \
break; \
} \
(!(__cond)); \
})
cond_timeout 宏
cond_timeout(cond, timeout)
// 就相当于
!cond
显然,如果这时 cond 成立了,cond_timeout 宏返回的就是 0 表示“等待 cond 条件没有超时”,否则 cond_timeout 宏返回 1 表示“未能等到 cond 条件成立,超时了”。
现在再将 cond_timeout 写入 main 函数,测试其是否可以正常工作,修改后的C语言代码如下,请看:
# gcc t.c -lpthread
# ./a.out
exit
因为 thread() 函数 2 秒后将 ready 置位了,所以 cond_timeout 宏没有返回超时。现在将 thread() 函数里的 sleep(2) 改为 sleep(6),相关C语言代码如下,请看:
void *thread(void *p)
{
sleep(6); // 修改为睡眠 6 秒
ready = 1;
return NULL;
}
# gcc t.c -lpthread
# ./a.out
timeout
exit
一切与预期一致。
小结
从本节可以看出,define 宏定义有时可以做到函数无法做到的事情。其实想想也能够明白,define 宏定义只是将C语言代码暂时“打包”,如果宏被调用,编译器就将包裹展开,这么看来,define 宏定义实际上只是将若干行代码取了一个名字而已。我们使用 gcc -E 命令获取编译器预处理后的C语言代码:
# gcc -E t.c
得到如下结果: