C语言陷阱与技巧第3节,怎样主动让出CPU?如何为C语言函数增加超时检测功能

在Linux C语言程序开发中,这个场景经常出现:进程 A 负责驱动数据采集装置获取数据,进程 B 则负责接收数据并处理。显然,进程 B 需要等待进程 A 将一次数据采集完毕才可以进行下一步工作,因此约定进程 A 采集一次数据完毕时,将 ready 位由 0 置 1,进程 B 监测 ready 位,若发现数据采集完毕,就开始数据处理。

适时地让出 CPU

如果数据到达之前,进程 B 的工作无法展开,那进程 B 显然应该时刻监测 ready 位由 0 变为 1,这样才能在数据准备完毕的第一时间开始处理数据的工作,这样的需求可以用类似下面这样的C语言代码解决,请看:

while(!ready);
/** 下一步工作 */

上面的C语言代码使用 while 循环不断检测 ready 位,一旦发现 ready 位被置 1,就立刻进行下一步的工作。这么做似乎绝对不会浪费一点点时间,因此是最好的做法。真的是这样吗?

如果整个系统只有进程 A 和进程 B,这么做的确很好。遗憾的是,如果系统里还有其他进程,上面这种做法就不太合适了,C语言程序遇到 while(!ready); 时,CPU 除了判断 ready 的值,其他什么都不做,这时,整个系统的效率就被拖累了。

虽然现代操作系统大都拥有“抢占式”的内核,但一般都是通过时间片轮转调度的,在 Linux 中,调度程序的周期一般在 10ms 量级。10ms 的时间,已经够 CPU 处理很多事情了。


如果数据的处理工作对时间的要求没有苛刻到连 10ms 都不能浪费,那么进程 B 在发现 ready 位没有被置位时,主动的将 CPU 让出给其他进程使用,显然可以提升整个系统的效率。因此,进程 B 等待 ready 置位的 C语言代码,按照下面这样写更加合适,请看:

while(!ready)
    usleep(10);
/** 下一步工作 */

usleep(10) 可以让进程 B 进入睡眠 10 us(微秒),但在 Linux 中,这么做能够使进程 B 将 CPU 暂时让出,交给操作系统分配给其他进程使用,进程 B 损失一点点时间效率,换来整个系统的效率提升。

超时判断,以及 usleep 的陷阱

如果进程 A 因为某种原因退出了,那 ready 位永远都不会被置 1,这将导致进程 B 卡死在下面这两行C语言代码:

while(!ready)
    usleep(10);


避免这种情况的一个好方法是增加超时判断,如果超过一段时间,进程 B 发现 ready 仍然没有被置位,就结束等待。要是进程 A 采集数据的周期是 t 秒,那超时时间就可以设置为 t+1 秒,这样就不会在进程 A 意外退出时,进程 B 仍然傻傻的等待 ready 位了。

不过,C语言并没有提供“超时”语法,这就需要程序员自己实现超时等待功能了。这种功能也并不难实现,假设超时时间为 5 秒,那相关的C语言代码可以如下实现:

int cnt = 50000;
while(!ready && cnt--)
    usleep(100);

使用上面这段C语言代码实现“超时等待”功能足够简单,但是可靠吗?假设 ready 位始终为 0,那上面这几行C语言代码就相当于下面这几行:

int cnt = 50000;
while(!ready && cnt--)
    usleep(100);


现在在 main() 函数中测试上述方法的实际睡眠时间,相关C语言代码如下,请看:

#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;
}


上述C语言代码中的 get_cur_ms() 函数可以获得当前时间的毫秒数,在测试“超时”代码之前,先使用 otime 记录当前时间 ms 数,然后在使用“超时”代码后的时间 ms 数减去 otime,就可以得到“超时”代码实际的等待时间了。编译这段C语言代码并执行,得到如下结果:

# ./a.out 
cur ms: 1553603227960
err ms: 8406

根据“超时”C语言代码段,预期时间差输出应该是 5 秒,也即 err ms 应该等于 5000,但是输出却是 8406,与预期相差 68%, 这是怎么回事呢?其实查看 usleep 的使用说明就可以得到答案了:

根据上述文档说明,usleep(usec) 会将线程挂起至少 usec 秒,而不是严格的 usec 秒,具体多少时间则取决于系统的调度。如果系统比较繁忙,那么 usleep(usec) 的实际睡眠时间可能会被延长,这就解释了预期睡眠 5 秒的C语言代码,最终却睡眠了 8 秒多。

那“超时”代码该怎么写呢?应该明白 gettimeofday() 函数可以获取微秒量级的时间,用于“超时”估计是非常合适的,请看下面的C语言代码:

 while(get_cur_ms()-otime < 5000)
         usleep(100); 


编译上述C语言代码并执行,可以得到如下输出:

# ./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;
 }


上述C语言代码使用 thread() 线程函数模拟进程 A 对 ready 标志位 3 秒后置 1,main() 函数若 5 秒仍未检测到 ready 被置 1,就提示 “time out”。编译上述C语言代码并执行,得到如下输出:

# 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() 函数误差常常会比较大,应该借助别的方法实现。

阅读更多:   C语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif