TBB库中实现协程(coroutine)的源码说明

    <link rel="stylesheet" href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/editerView/kdoc_html_views-1a98987dfd.css">
    <link rel="stylesheet" href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/editerView/ck_htmledit_views-25cebea3f9.css">
            <div id="content_views" class="htmledit_views">
                <p>源码请见:&nbsp;<a href="https://github.com/oneapi-src/oneTBB/blob/master/src/tbb/co_context.h" title="https://github.com/oneapi-src/oneTBB/blob/master/src/tbb/co_context.h">https://github.com/oneapi-src/oneTBB/blob/master/src/tbb/co_context.h</a></p> 

windows系统,TBB(也就是intel 的 oneTBB库),通过windwos fiber(纤程)来实现协程(coroutine)。

创建一个协程,代码很简洁:


 
 
  1. inline void create_coroutine(coroutine_type& c, std::size_t stack_size, void* arg) {
  2. __TBB_ASSERT(arg, nullptr);
  3. c = CreateFiber(stack_size, co_local_wait_for_all, arg);
  4. __TBB_ASSERT(c, nullptr);
  5. }

windows系统中线程(thread)与纤程(fiber)调度示意图,如下图所示

windwos中的纤程和通常说的协程类似,处在用户模式下,和内核态无关不会有切换复杂上下文的开销。一个线程一次只能执行一个纤程,实际单个线程上的上多纤程利用时间片形成并发机制。进程与线程是内核态相关的操作机制,调度过程是抢占式的。而协程调度是在用户态完成的,需要代码里显式地将CPU调度交给其他协程,这是协作式的。

在不考量多核心cpu算力扩展的情况下,只谈调度效率或者一个程序对单个cpu的利用率,协程要高效得多。而且,本质上来说,一个线程里面的协程是没有并行机制里面的数据竞争的,这意味着保证同步正确性(没有数据竞争问题)的同时具有了异步灵活性。

将 CPU 的执行从一个线程切换到另一个线程,不可避免地涉及内核调度机制,这是个昂贵的开销操作,如果两个线程经常频繁地来回切换则代价尤其大。 Windows 实现了两种机制来降低这一开销:纤程(fiber)和用户模式调度(UMS , user-mode scheduling)。
纤程使得一个应用程序可以调度它自己的“线程”的执行过程,而不必依赖于 Windows 内置的基于优先级的调度机制。纤程也常被称为“轻量”线程:从调度的角度来看,它们对于内核是不可见的,因为它们是在用户模式下在 Kemel32.dll 中实现的。为了使用纤程,首先要调用 Windows 的 ConvertThreadToFiber 函数。该函数将当前线程转变成一个正在运行的纤程。之后,在转变得到的纤程中,通过调用 CreateFiber 函数,又可以创建额外的纤程(每个纤程可以有它自己的一组纤程)。然而,与线程不同的是,纤程不会自动执行,它必须由 SwitchToFiber 函数手工选中,然后才能执行。新的纤程会一直运行,直到退出,或者调用SwitchToFiber再次选择运行另一个纤程。

感谢: https://www.cnblogs.com/5iedu/p/4830983.html

在linux系统下使用glibc中的ucontext库实现,比起基于windows纤程的协程实现,这个实现要复杂一些。


 
 
  1. inline void create_coroutine(coroutine_type& c, std::size_t stack_size, void* arg) {
  2. const std:: size_t REG_PAGE_SIZE = governor:: default_page_size();
  3. const std:: size_t page_aligned_stack_size = (stack_size + (REG_PAGE_SIZE - 1)) & ~(REG_PAGE_SIZE - 1);
  4. const std:: size_t protected_stack_size = page_aligned_stack_size + 2 * REG_PAGE_SIZE;
  5. // Allocate the stack with protection property
  6. std:: uintptr_t stack_ptr = (std:: uintptr_t) mmap( nullptr, protected_stack_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
  7. __TBB_ASSERT(( void*)stack_ptr != MAP_FAILED, nullptr);
  8. // Allow read write on our stack (guarded pages are still protected)
  9. int err = mprotect(( void*)(stack_ptr + REG_PAGE_SIZE), page_aligned_stack_size, PROT_READ | PROT_WRITE);
  10. __TBB_ASSERT_EX(!err, nullptr);
  11. // Remember the stack state
  12. c.my_stack = ( void*)(stack_ptr + REG_PAGE_SIZE);
  13. c.my_stack_size = page_aligned_stack_size;
  14. err = getcontext(&c.my_context);
  15. __TBB_ASSERT_EX(!err, nullptr);
  16. c.my_context.uc_link = nullptr;
  17. // cast to char* to disable FreeBSD clang-3.4.1 'incompatible type' error
  18. c.my_context.uc_stack.ss_sp = ( char*)c.my_stack;
  19. c.my_context.uc_stack.ss_size = c.my_stack_size;
  20. c.my_context.uc_stack.ss_flags = 0;
  21. typedef void(*coroutine_func_t)();
  22. std:: uintptr_t addr = std:: uintptr_t(arg);
  23. unsigned lo = unsigned(addr);
  24. unsigned hi = unsigned(std:: uint64_t(addr) >> 32);
  25. __TBB_ASSERT( sizeof(addr) == 8 || hi == 0, nullptr);
  26. makecontext(&c.my_context, ( coroutine_func_t)co_local_wait_for_all, 2, hi, lo);
  27. }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值