我有一个字符应该"吃";持续200微秒;睡眠;持续200微秒,并重复,直到它们死亡,如果它们在time_to_die
微秒内没有进食,就会发生这种情况。
在下面指出的函数main
的代码片段中,结构体time_to_die
有一个成员tv_usec
,配置时间为1000微秒,我希望它永远循环。
一段时间后,函数busy_wait
的一次执行所花费的时间大约是预期的5倍(足以杀死角色),角色就会死亡。我想知道为什么以及如何修复它。
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
struct timeval time_add_microseconds(struct timeval time, long long microseconds)
{
time.tv_usec += microseconds;
while (time.tv_usec >= 1000000)
{
time.tv_sec += 1;
time.tv_usec -= 1000000;
}
return (time);
}
short time_compare(struct timeval time_one, struct timeval time_two)
{
if (time_one.tv_sec != time_two.tv_sec)
{
if (time_one.tv_sec > time_two.tv_sec)
return (1);
else
return (-1);
}
else
{
if (time_one.tv_usec > time_two.tv_usec)
return (1);
else if (time_one.tv_usec == time_two.tv_usec)
return (0);
else
return (-1);
}
}
// Wait until interval in microseconds has passed or until death_time is reached.
void busy_wait(int interval, struct timeval last_eaten_time, struct timeval time_to_die)
{
struct timeval time;
struct timeval end_time;
struct timeval death_time;
gettimeofday(&time, NULL);
end_time = time_add_microseconds(time, interval);
death_time = time_add_microseconds(last_eaten_time, time_to_die.tv_sec * 1000000ULL + time_to_die.tv_usec);
while (time_compare(time, end_time) == -1)
{
gettimeofday(&time, NULL);
if (time_compare(time, death_time) >= 0)
{
printf("%llu diedn", time.tv_sec * 1000000ULL + time.tv_usec);
exit(1);
}
}
}
int main(void)
{
struct timeval time;
struct timeval time_to_die = { .tv_sec = 0, .tv_usec = 1000};
struct timeval last_eaten_time = { .tv_sec = 0, .tv_usec = 0 };
while (true)
{
gettimeofday(&time, NULL);
printf("%llu eatingn", time.tv_sec * 1000000ULL + time.tv_usec);
last_eaten_time = time;
busy_wait(200, last_eaten_time, time_to_die);
gettimeofday(&time, NULL);
printf("%llu sleepingn", time.tv_sec * 1000000ULL + time.tv_usec);
busy_wait(200, last_eaten_time, time_to_die);
}
}
注意:除了我在代码中已经使用的系统函数之外,我只允许使用usleep、write、malloc和free。
谢谢你抽出时间。
注意:我错误地将毫秒与微秒混淆了,这一直是问题所在。。。
一段时间后,函数busy_wait的一次执行所需时间大约是预期时间的5倍(足以杀死角色),角色就会死亡。我想知道为什么以及如何修复它。
有多种可能性。它们中的许多都围绕着这样一个事实:当程序运行时,计算机中发生的事情比程序运行时还要多。除非您在实时操作系统上运行,否则最重要的是您不能修复可能导致此类行为的一些问题。
例如,您的程序与系统本身以及系统上运行的所有其他进程共享CPU。这可能比您想象的要多:目前,我的6核工作站上有400多个实时进程。当需要CPU时间的进程比运行它们的CPU多时,系统将在竞争进程之间分配可用时间,在轮次到期时先发制人地暂停进程。
如果您的程序在繁忙的等待过程中被抢占,那么在CPU上的下一次调度之前,很有可能会经过超过200μs的壁时间。时间片大小通常以毫秒为单位,在通用操作系统上,从一个程序的过去到同一程序的下一个程序开始之间的时间没有上限(或下限)。
正如我在评论中所做的,我注意到您正在使用gettimeofday
来测量经过的时间,但这不在您允许的系统功能列表中。这种不一致性的一个可能解决方案是,您不打算对经过的时间执行测量,而是假设/模拟。例如,usleep()
在列表中,因此您可能要usleep()
而不是忙于等待,并假设睡眠时间正是请求的时间。或者,您可能只是想调整一个内部时间计数器,而不是真正暂停执行。
为什么
最终:因为中断或陷阱被传递到执行程序的CPU核心,从而将控制权转移到操作系统。
一些常见原因:
-
操作系统使用定期启动的硬件计时器运行进程调度。也就是说,操作系统正在运行某种公平的调度程序,它必须检查您的进程的时间是否到了。
-
系统中的某些设备需要维修。例如,一个数据包通过网络到达,声卡的输出缓冲区不足,必须重新填充,等等。
-
你的程序会主动向操作系统发出请求,操作系统会将控制权转移给它。基本上:无论何时你进行系统调用,内核都可能需要等待I/O,或者它可能决定是时候调度不同的进程了,或者两者兼而有之。在您的情况下,对
printf
的调用将在某个时刻导致写(2)系统调用,该调用最终将执行一些I/O。
该怎么办
原因3可以通过确保不进行系统调用来避免,即永远不会陷入操作系统。
原因1和2非常难以完全消除。您实际上是在寻找一个实时操作系统(RTOS)。像Linux这样的操作系统可以通过将进程放置在不同的调度域(SCHED_FIFO
/SCHED_RR
)中来近似这一点。如果您愿意切换到针对实时应用程序定制的内核,您可以做得更远。您还可以查看诸如";"CPU隔离";。
为了说明printf,同时获取评论中提到的当天时间,我尝试了两件事
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int main(void)
{
struct timeval time;
long long histo[5000];
for(int i=0; i<5000; i++){
gettimeofday(&time, NULL);
histo[i]=time.tv_sec * 1000000ULL + time.tv_usec;
}
long long min=1000000000;
long long max=0;
for(int i=1; i<5000; i++){
long long dt=histo[i]-histo[i-1];
if(dt<min) min=dt;
if(dt>max) max=dt;
if(dt>800) printf("%d %lldn", i, dt);
}
printf("avg: %f min=%lld max=%lldn", (histo[4999]-histo[0])/5000.0, min, max);
}
所以它在这里所做的只是在5000次printf/gettimeofday迭代中循环。然后测量(环路后)平均值、最小值和最大值
在我的X11终端(Sakura)上,平均每个循环8μs,最小1μs,最大3790μs!(我做的其他测量表明,这3000μs左右也是唯一一个超过200μs的。换句话说,它永远不会超过200μs.除非它"很大")。所以,平均来说,一切都很顺利。但偶尔,printf需要将近4ms的时间(这还不够,这种情况不会连续发生几次,人类用户甚至不会注意到。但这远远超出了让代码失败的需要)。
在我的控制台(没有X11)终端上(一个80x25终端,可能使用,也可能不使用我的显卡的文本模式,我从来没有确定过),平均值为272μs,最小值为193μs,最大值为1100μs。这(追溯)并不奇怪。这种终端速度慢,但更简单,因此不太容易出现"故障";惊喜";。但是,好吧,它失败得更快,因为超过200μs的概率非常高,即使没有超过很多,超过一半的循环也需要超过200μs。
我还尝试了在没有printf的循环上进行测量。
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int main(void)
{
struct timeval time;
long long old=0;
long long ntot=0;
long long nov10=0;
long long nov100=0;
long long nov1000=0;
for(int i=0;;i++){
gettimeofday(&time, NULL);
long long t=time.tv_sec * 1000000ULL + time.tv_usec;
if(old){
long long dt=t-old;
ntot++;
if(dt>10){
nov10++;
if(dt>100){
nov100++;
if(dt>1000) nov1000++;
}
}
}
if(i%10000==0){
printf("tot=%lld >10=%lld >100=%lld >1000=%lldn", ntot, nov10, nov100, nov1000);
old=0;
}else{
old=t;
}
}
}
因此,它衡量的是我可以浮夸地称之为";对数直方图";时间。这一次,独立于终端(每次我打印一些东西时,我都会把旧的放回0,这样这些时间就不算了)
结果
tot=650054988 >10=130125 >100=2109 >1000=2
所以,可以肯定的是,99.98%的时间,每天的获取时间不到10μs。但是,每数百万次调用3次(这意味着,在您的代码中,只需几秒钟),所需时间超过100μs。在我的实验中,有两次花费了超过1000μs。只是得到一天的时间,而不是打印。
显然,这不是一天中花费1毫秒的时间。但简单地说,我的系统上发生了更重要的事情,该进程必须等待1ms才能从调度器获得一些cpu时间。
请记住,这是在我的电脑上。在我的电脑上,你的代码运行得很好(好吧,这些测量表明,如果我让它运行,只要我让这些测量运行,它最终就会失败)。
在你的电脑上,这些数字(2>1000)肯定要多得多,所以它很快就会失败。
抢占式多任务操作系统根本不能保证以微秒为单位的执行时间。你必须使用实时操作系统(例如RT-linux。无论如何,它是存在的——我自2002年以来就没有使用过它)。
正如其他答案中所指出的,在我的限制范围内,如果不对其设计进行重大更改,就无法使此代码按我的预期工作。因此,我更改了代码,使其不依赖于gettimeofday
来确定哲学家是否去世,或确定打印的时间值。相反,我只是在角色每次吃饭/睡觉时向time
添加200μs。这确实是一个廉价的把戏。因为当一开始我显示正确的系统墙时间时,随着程序的运行,我的时间变量会越来越多地与系统时间不同,但我想这就是我想要的。
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
struct timeval time_add_microseconds(struct timeval time, long long microseconds)
{
time.tv_usec += microseconds;
while (time.tv_usec >= 1000000)
{
time.tv_sec += 1;
time.tv_usec -= 1000000;
}
return (time);
}
short time_compare(struct timeval time_one, struct timeval time_two)
{
if (time_one.tv_sec != time_two.tv_sec)
{
if (time_one.tv_sec > time_two.tv_sec)
return (1);
else
return (-1);
}
else
{
if (time_one.tv_usec > time_two.tv_usec)
return (1);
else if (time_one.tv_usec == time_two.tv_usec)
return (0);
else
return (-1);
}
}
bool is_destined_to_die(int interval, struct timeval current_time, struct timeval last_eaten_time, struct timeval time_to_die)
{
current_time = time_add_microseconds(current_time, interval);
if ((current_time.tv_sec * 1000000ULL + current_time.tv_usec) - (last_eaten_time.tv_sec * 1000000ULL + last_eaten_time.tv_usec) >= time_to_die.tv_sec * 1000000ULL + time_to_die.tv_usec)
return (true);
else
return (false);
}
// Wait until interval in microseconds has passed or until death_time is reached.
void busy_wait(int interval, struct timeval current_time, struct timeval last_eaten_time, struct timeval time_to_die)
{
struct timeval time;
struct timeval end_time;
struct timeval death_time;
gettimeofday(&time, NULL);
if (is_destined_to_die(interval, current_time, last_eaten_time, time_to_die))
{
death_time = time_add_microseconds(last_eaten_time, time_to_die.tv_sec * 1000000 + time_to_die.tv_usec);
while (time_compare(time, death_time) == -1)
gettimeofday(&time, NULL);
printf("%llu diedn", time.tv_sec * 1000000ULL + time.tv_usec);
exit(1);
}
end_time = time_add_microseconds(time, interval);
while (time_compare(time, end_time) == -1)
gettimeofday(&time, NULL);
}
int main(void)
{
struct timeval time;
struct timeval time_to_die = { .tv_sec = 0, .tv_usec = 1000};
struct timeval last_eaten_time = { .tv_sec = 0, .tv_usec = 0 };
gettimeofday(&time, NULL);
while (true)
{
printf("%llu eatingn", time.tv_sec * 1000000ULL + time.tv_usec);
last_eaten_time = time;
busy_wait(200, time, last_eaten_time, time_to_die);
time = time_add_microseconds(time, 200);
printf("%llu sleepingn", time.tv_sec * 1000000ULL + time.tv_usec);
busy_wait(200, time, last_eaten_time, time_to_die);
time = time_add_microseconds(time, 200);
}
}