11
2020
03

call_out()函数小结

转:(我也不知道哪年从internat上 copy save下来的)

因为reallove的帖子,我算是第一次较为仔细的看了一遍call_out()在driver中的机理,也明确了不少以前似是而非的概念。现整理如下:


1. 关于call_out()的driver级宏开关有两个(均在options.h当中):


    a. THIS_PLAYER_IN_CALL_OUT


    该宏被define时,我们可以在被延时呼叫的函数中肆无忌惮的使用this_player()来找到call_out定义时的this_player(),反之则无法找到准确的this_player()(也就是说,可能找到一个谁,但这个“谁”应该与定义call_out时的“谁”毫无瓜葛,肯定不是我们希望的那一个就对了)


    b. CALLOUT_HANDLES


    该宏被定义时,call_out()原型变成了 int call_out(function foo,int delay, ..),返回值是该call_out的唯一句柄,我们可以在remove_call_out()时以此为参数清理掉“这一个”而非“这一类”call_outs


 


2. 关于call_out的作用时机和delay的详细含义:


    已知call_out记录是以链表与delay的某个余数分类的形式进行存储的。


    call_out会在每个系统心跳(不是每个人的心跳,而是说,系统每次呼叫完所有心跳)之后进入对call_out们的处理(仔细的说,driver中call_out.c中的call_out()函数就是干这个事情的)。


    因此,就我现在阅读driver代码得到的结论,可以确定的说,call_out一定在心跳点上发作,但会有累积发作的模式。


    


   在解释delay之前,有几个“时间”需要说明:


   a. heart_beat()的发作时间:driver通过一个alarm/singal事件来设置hb的标志位,并在backend()的大循环当中不停检查该标志位的状态以决定是否应该触发hb,因此通常可以认为hb的发作较为准确。虽然从代码中看,在不同系统下的编译中,driver使用了alarm和ualarm两种不同方式进行按秒或按微秒级别的触发事件,但不那么精确的描述下,还是可以认为心跳的准时程度较高,更接近与真实系统时间。


   b. call_out被插入和判断是否被执行(注意不是是否进入call_out执行过程,而是进入后的“判断”),依据的是另外一个时间标记current_time。该标记在系统启动的时候被设置为当前时间的秒数(unix_time,也就是我们常看到的1239xxxxxxxx这种),其后只在系统心跳时被重置为“当前”。因此这个时间标并不总是等于真实的时间。特别是在hb大于1秒的情况下,如果以真实的秒为ticker来审视它,可能会是:0,0,0,[1,2,3],3,3,3,[4,5,6]这样的情况,即很多秒不变化,然后在同一个时间点上依次++直到追上真实时间。


 


    于是,关于delay有如下看法:


    a.在插入一条新的call_out时,delay看起来是从“现在”加上delay秒数后执行被呼叫函数,但这个“现在”是current_time指定的现在,hb>1sec时,他通常指的是过去。。。,因此当我们call_out(foo,3)的时候,他不一定指的是现在+3秒,而是指time()+3秒,他也不一定真的在三秒后执行。不考虑毫秒位的话,通常只有hb为1秒的时候,上面的描述才较为精确。但另外一个方面,delay的含义更倾向于秒,而不是hb。。。(我觉得我已经无法用人类的语言解释清楚这个破玩意儿了。。。。)


    b.如前述,call_out被执行一定是在hb的执行点上,具体的执行方式是:在某个HB执行点上,系统扫描小于current_time的call_out记录,然后依次执行(链表的原因,对于应该在“同一个时间点被执行的callout”,先入先执行)。然后系统对current_time++,继续上一步操作,知道current_time追赶上“现在”。


 


    所以说,如果你期望call_out表现比较正常,那最好把hb设为1秒(1000000微秒);否则hb越大,callout的表现就越奇怪。hb非整秒数则会带来其他更微妙的改变。。。。


 


 


    最后,关于call_out(foo,0)这个特例。


    一般来说,我们无法期待这个东西立刻执行。他可能会在下一次hb的时候才被执行。


    之所以用“可能”,是因为这个特例还有个更奇怪的特例。该call_out本身处于另外一个call_out的呼叫函数当中。因此这个call_out进行的记录插入动作本身就是在call_out执行点上。可想而知,他立刻就被执行了!!!


 


    我们举两个例子来说明吧:


    我们假设hb是10个seconds。查看这样一段函数:


void test()


{


        call_out("test_callout",0);


}


 


void test_callout()


{


        tell_object(this_object(),sprintf("CO:%d\n",time()));


        call_out("test_callout",0);


}


 


当我们以某种形式执行了test()的时候,会出现什么状况呢?


首先是test()中的:call_out("test_callout",0)被执行,插入了一条记录,这个记录应当被触发的时间点是current_time+0。(我们假设current_time是333秒吧)


但考虑到hb点还没到。这个时候我们什么都看不到。


等到下一个hb点,也许作为现实时间,是337秒的时候吧(举例)。


上次插入的callout被执行了!


因此我们收到一条CO:333的显示信息(因为current_time这个时候还没++完呢)


接下来就坏掉了,因为又一个call_out("test_callout",0)被执行了!


要注意,这一条call_out插入的触发时间点依然是current_time+0,也就是333。


这时这个test_callout函数被执行完,退出,回到系统的call_out执行循环当中。


坏掉了,系统发现在333这个时间点上还有个未处理的记录(刚插进去的。。)


于是进入了死循环。。。


我们会看到系统不断刷屏CO:333


CO:333


CO:333


CO:333


。。。。


死循环下去了。


 


上面这个例子表明,callout 0是否立刻被执行,要看他所处的环境,不能一概而论,即时hb是1也如此。


 


再来一个例子:


同样hb是10seconds。


 


void test()


{


        call_out("test_callout",0);


}


 


void test_callout(){


        tell_object(this_object(),sprintf("CO:%d\n",time()));        call_out("test_callout",1);


}差别是后一个call_out的delay是1。


 


不详细描述了,我们得到的结果应该是类似:


系统等待发呆。。。。一次性刷屏:


CO:333


CO:334


CO:335


CO:336


CO:337


继续等待发呆个10秒。。。一次性刷屏:


CO:338


CO:339


CO:340


CO:341


CO:342


CO:343


CO:344


CO:345


CO:346


CO:347


等待。。。刷屏。。。等待。。。刷屏。。。。

————————————————

版权声明:本文为CSDN博主「回到未来望过去」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/viviliving/article/details/100553285


« 上一篇下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。