C语言陷阱与技巧第3节,怎样主动让出CPU?如何为C语言函数增加超时检测功能
发表于: 2019-04-28 08:18:43 | 已被阅读: 51 | 分类于: C语言
在Linux C语言程序开发中,这个场景经常出现:进程 A 负责驱动数据采集装置获取数据,进程 B 则负责接收数据并处理。显然,进程 B 需要等待进程 A 将一次数据采集完毕才可以进行下一步工作,因此约定进程 A 采集一次数据完毕时,将 ready 位由 0 置 1,进程 B 监测 ready 位,若发现数据采集完毕,就开始数据处理。
适时地让出 CPU
while(!ready);
/** 下一步工作 */
上面的C语言代码使用 while 循环不断检测 ready 位,一旦发现 ready 位被置 1,就立刻进行下一步的工作。这么做似乎绝对不会浪费一点点时间,因此是最好的做法。真的是这样吗?
如果整个系统只有进程 A 和进程 B,这么做的确很好。遗憾的是,如果系统里还有其他进程,上面这种做法就不太合适了,C语言程序遇到 while(!ready); 时,CPU 除了判断 ready 的值,其他什么都不做,这时,整个系统的效率就被拖累了。
虽然现代操作系统大都拥有“抢占式”的内核,但一般都是通过时间片轮转调度的,在 Linux 中,调度程序的周期一般在 10ms 量级。10ms 的时间,已经够 CPU 处理很多事情了。
while(!ready)
usleep(10);
/** 下一步工作 */
usleep(10) 可以让进程 B 进入睡眠 10 us(微秒),但在 Linux 中,这么做能够使进程 B 将 CPU 暂时让出,交给操作系统分配给其他进程使用,进程 B 损失一点点时间效率,换来整个系统的效率提升。
超时判断,以及 usleep 的陷阱
如果进程 A 因为某种原因退出了,那 ready 位永远都不会被置 1,这将导致进程 B 卡死在下面这两行C语言代码:
while(!ready)
usleep(10);
不过,C语言并没有提供“超时”语法,这就需要程序员自己实现
int cnt = 50000;
while(!ready && cnt--)
usleep(100);
使用上面这段C语言代码实现“超时等待”功能足够简单,但是可靠吗?假设 ready 位始终为 0,那上面这几行C语言代码就相当于下面这几行:
int cnt = 50000;
while(!ready && cnt--)
usleep(100);
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
long get_cur_ms()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec*1000 + tv.tv_usec/1000;
}
int main()
{
long otime = get_cur_ms();
printf("cur ms: %ld\n", otime);
int cnt = 50000;
while(cnt--)
usleep(100);
printf("err ms: %ld\n", get_cur_ms()-otime);
return 0;
}
# ./a.out
cur ms: 1553603227960
err ms: 8406
根据“超时”C语言代码段,预期时间差输出应该是 5 秒,也即 err ms 应该等于 5000,但是输出却是 8406,与预期相差 68%, 这是怎么回事呢?其实查看 usleep 的使用说明就可以得到答案了:
那“超时”代码该怎么写呢?应该明白 gettimeofday() 函数可以获取微秒量级的时间,用于“超时”估计是非常合适的,请看下面的C语言代码:
while(get_cur_ms()-otime < 5000)
usleep(100);
# ./a.out
cur ms: 1553608462083
err ms: 5000
可以看出,以上C语言代码的确提供了更加精确的“超时”功能。
感兴趣的读者可以自己掐秒表测试这两种“超时”C语言代码。
测试自定义的超时功能
请看下面的C语言代码:
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
#include <pthread.h>
int ready = 0;
long get_cur_ms()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec*1000 + tv.tv_usec/1000;
}
void *thread(void *p)
{
sleep(3);
ready = 1;
return NULL;
}
int main()
{
pthread_t pid;
pthread_create(&pid, NULL, thread, NULL);
long otime = get_cur_ms();
while( !ready && get_cur_ms()-otime < 5000)
usleep(100);
if(get_cur_ms()-otime >= 5000)
printf("time out\n");
printf("err ms: %ld\n", get_cur_ms()-otime);
return 0;
}
# gcc t.c -lpthread
# ./a.out
err ms: 3000
因为 thread() 函数 3 秒后将 ready 置 1了,所以没有超时。现在将 thread() 中的 sleep(3) 改为 sleep(6),编译修改后的C语言代码并执行,得到如下输出:
小结
从本节可知,在 Linux C语言程序开发中,需要使用 while 循环不断检查某标志位是否置位时,若对时间的要求不是特别苛刻,可以适当调用 usleep() 函数主动让出 CPU,以提升系统的整体效率。另外,若需要增加超时功能,使用 usleep() 函数误差常常会比较大,应该借助别的方法实现。