准备学习boost::fiber库
这里写自定义目录标题
- 写在前面
- 我的需求和来源
- 现状:如何停一个纤程
- 这个回答的问题
- 问题的本质
- 为什么没有人做这件事
- 在一个受保护环境中
写在前面
虽然我的人生并不是可控的,但这些年我的方向基本上还是在并行计算相关的底层,算是有几分幸运。虽然学了许多知识,但我清楚,有一样技术,我感兴趣,世界也很需求,但是好像人们并不是真正关注和理解。
未来的世界需要的是一种随时停止的纤程。
这个一会我再解释。
我与kimi进行了激烈的讨论,了解了一些问题。
先说结论:最终它的建议是,我们可以试试boost::fiber。
我的需求和来源
之前的工作中,研究和维护Intel的Flex Ran,有近3年的时间。
我自己认为,是学到许多知识。
以前在Windriver是站在操作系统os的视解来思考问题;
但是Flex Ran 给了我另一种视角。
例如,每500us,有25个微任务(也要task),需要微内核来调度,当然这个微内核就是flexran的调度器。是一个用户面程。
核隔离,巨页内存,亲和性,dpdk(驱动运行于用户面),整个这套体系,都是运行于用户面,而不是利用操作系统来调度。
这些想法,我认为是超前的,也是很有价值的。当然,intel的问题,在于其目的的不存粹:总是想着与其服务器绑定,到处是avx512 的代码,这些是它的问题,这里我们不关注,因为与并行计算的需求无关。
另一个严重的问题,以前我可能提到过,是intel的服务器围绕不会产生错误数据存到硬盘上这一互联网理念来实现,这种观念是严重不符合通信、自动驾驶,这些当代实时并行计算的边缘计算的领域的!
这些领域实时性要求远高于数据正确性。
这个我们不展开说了。
但是,我们再回到基本问题:如何停止一个微任务呢?
FlexRan并没有给出合理的答案。
现状:如何停一个纤程
我很关注一件事,以windows的纤程为例,如果在一个一程中,有15个纤程,这15个纤程之间的调度,是如何实现的?能否做到,如果我发现一个纤程,占用的时间过长,已经处理超时,没有必要再运算下去,而直接由调度器参与,停止这个纤程,等下一帧数据到达后,这个纤程还能与其它的纤程一起协同工作?
kimi的回答:
在Windows系统中,纤程(Fiber)的调度是通过程序员手动控制的,而不是由操作系统内核调度。这意味着,在一个线程中的15个纤程之间的调度需要开发者自己编写代码来实现。以下是如何实现纤程调度以及处理纤程超时的机制:
-
纤程的调度:
- 在Windows中,纤程的调度是通过
SwitchToFiber
函数实现的。这个函数允许你在不同的纤程之间进行切换。例如,如果你有一个主纤程和多个工作纤程,你可以在主纤程中根据需要调用SwitchToFiber
来切换到不同的工作纤程。
- 在Windows中,纤程的调度是通过
-
纤程的停止和恢复:
- 如果你发现一个纤程占用时间过长,你可以通过检查纤程执行的时间来判断是否超时。如果纤程超时,你可以选择不切换到该纤程,而是切换到其他纤程。纤程本身并没有内置的停止机制,但是你可以通过编程逻辑来控制纤程的执行流程。例如,你可以设置一个标志变量来指示纤程是否应该继续执行,然后在纤程的执行逻辑中检查这个标志。
-
纤程的协同工作:
- 纤程可以与同一线程中的其他纤程协同工作。当一个纤程完成其任务后,可以通过
SwitchToFiber
函数切换到另一个纤程。如果纤程需要等待下一帧数据到达,它可以主动切换回主纤程或另一个纤程,然后在适当的时候再次被切换回来执行。
- 纤程可以与同一线程中的其他纤程协同工作。当一个纤程完成其任务后,可以通过
-
纤程的删除:
- 如果一个纤程不再需要,可以使用
DeleteFiber
函数来删除它。需要注意的是,如果当前正在运行的纤程调用DeleteFiber
,将会导致它所在的线程退出。
- 如果一个纤程不再需要,可以使用
-
纤程的上下文切换:
- 纤程的上下文切换是通过
SwitchToFiber
函数显式进行的,这意味着纤程的调度完全由程序员控制。这与内核线程的调度不同,后者是由操作系统内核根据线程的优先级和调度策略自动进行的。
- 纤程的上下文切换是通过
总结来说,纤程的调度和超时处理需要开发者自己通过编程逻辑来实现。纤程提供了一种在用户模式下进行线程调度的机制,使得开发者可以更细粒度地控制任务的执行流程,但这也意味着需要更多的编程工作来管理纤程的生命周期和调度。
这个回答的问题
这个回答,无非是相信每个被管对象都国王:不会犯错。
这显然是不成立的。
实际上,我们翻看许多书,许多互联网的内容,都有这样的问题:人们一代代继承着明显违背最基本常识的观点:如果每个纤程都不犯错,还需要调度者吗?
显然,kimi的回答,很大程度上,是吸收了这种见什么人唱什么歌的说法。
问题的本质
问题的本质,是如何在和平的条件下,直接reset一个纤程的指针。
这句话分为两个场景,一个是如果时间片分得很细,在一个线程中,比如每个转轮,所有的用户纤程和调度纤程,都能公平得到调度(这其实是有点困难,但我们先假定这可以成立,因为这个假定是合理的,因为这个假定与当前两个角色无关),那么调度者能够判断哪个业务纤程超时了,从而将这个纤程的栈指针reset到开始的位置,也就是将当前帧数据抛弃。
第二个场景是这个超时的纤程是活跃的,正在运行中的。这种情况下的超时,这个我们暂时不做考虑。因为我们先假定前面我们说的那个强行调度的管理者,是有效的。不会允许任何一个可能跑飞的纤程长时间有运行权。
所以,问题的本质就变成了,reset栈指针。
为什么没有人做这件事
OS是无法实现这个需求的。因为OS强调着一切计算机学科里的理论,例如生命周期。reset 一个线程的栈指针,将导致未预期和未定义的行业。
是什么意思呢?意思是,决定者和执行者,在做了这件事之后,将不清楚自己是否能幸存。
OS是很脆弱的程序,它的稳健来自于它坚决地尊守这些信条。因为线程的栈的维护,是由OS来实现的,它如果任意移动,这显然,是破坏了冯.诺伊曼 模型最起码的基础。
但是,在客观世界,有这样的需求。
这个需求在未来只会越来越强烈。
比如,我为什么坚持每周抽一点时间给孩子讲课,其中一个原因是老师,必须坚持绝对正确。
但是,许多题,在特定的场景中,是有很多规则之外的解决办法的。
对于这些选择题来说,这些看似并不严谨的办法,给让你在最短的时间得到正确的答案。
我这么解释,可能您是能理解的。
在一个受保护环境中
能否我们换一种问法,假如调度者能够感知道某个纤程超时,它有能力将这个纤程冻结,并且将其调用栈的指针重置于纤程起始,等下一轮的数据到达。这个过程中是和平交接的,没有异常出现。不需要,也绝不应当由异常来处理。因为异常的设计与这种需求无关。异常也无法保证实时性,它只能将事情搞得更糟糕。我想了解一下,是否有这样的技术。
然后得到这样的信息,我计划研究一下:
根据您的需求,您想要实现的是在纤程(Fiber)执行超时后,能够由调度器和平地冻结纤程,并将纤程的调用栈指针重置到纤程起始位置,以便在下一轮数据到达时纤程可以继续执行。这种需求可以通过用户态的纤程库来实现,而不是依赖于操作系统内核的异常处理机制。
在Linux系统中,虽然没有直接提供与Windows纤程相同的机制,但是可以通过一些用户态的纤程库来实现类似的功能。例如,C++ Boost库中的纤程(Fiber)提供了轻量级的用户态线程,可以实现协程的功能,并可以通过中断或等待的方式控制纤程的执行时间
。
具体来说,可以使用Boost库中的boost::fibers::future和boost::fibers::promise来实现超时控制。首先,创建一个promise对象,并通过get_future()方法获取与之关联的future对象。在纤程中使用boost::fibers::future::wait_for()方法来等待future对象的完成状态,并设置等待的最大时间。如果纤程执行超时,可以通过fiber.interrupt()方法中断纤程
。
此外,还可以通过纤程调度器(Fiber Scheduler)接口实现阻塞操作的异步行为。纤程调度器允许在阻塞操作发生时插入异步行为,类似于带有反转的回调:当异步回调被执行时,主阻塞方法不会运行
。
在实现纤程调度器时,可以通过维护纤程的状态来确保纤程不会被同时调度多次,并在纤程超时时将其状态设置为暂停或重置,从而实现纤程的和平交接
。
综上所述,通过使用用户态纤程库和纤程调度器,可以实现您所描述的需求,即在纤程超时后由调度器和平地冻结纤程,并在下一轮数据到达时纤程可以继续执行,而不需要依赖于操作系统内核的异常处理机制。