为什么80%的码农都做不了架构师?>>>   

tbox之前提供的stackfull协程库,虽然切换效率已经非常高了,但是由于每个协程都需要维护一个独立的堆栈, 内存空间利用率不是很高,在并发量非常大的时候,内存使用量会相当大。

之前考虑过采用stacksegment方式进行内存优化,实现动态增涨,但是这样对性能还是有一定的影响,暂时不去考虑了。

最近参考了下boost和protothreads的stackless协程实现,这种方式虽然易用性和灵活性上受到了很多限制,但是对切换效率和内存利用率的提升效果还是非常明显的。。

因此,我在tbox里面也加上了对stackless协程的支持,在切换原语上参考了protothreads的实现,接口封装上参考了boost的设计,使得更加可读易用

先晒段实际的接口使用代码:

tb_lo_coroutine_enter(coroutine)
{while (1){tb_lo_coroutine_yield();}
}

然后实测对比了下:

* 切换性能在macosx上比tbox的stackfull版本提升了5-6倍,1000w次切换只需要40ms
* 每个协程的内存占用也减少到了只有固定几十个bytes

那么既然stackless的效率提升这么明显,stackfull模式还需要吗?可以比较下两者的优劣:

  • stackfull协程:易用性和灵活性非常高,但是内存使用过大
  • stackless协程:切换效率和内存利用率很高,更加轻量,但是使用上限制较多

由于stackless的实现比较轻量,占用资源也不是很多,因此tbox默认放置到了micro微内核模式下,作为基础模块,提供股嵌入式平台使用

而一般情况下,如果对资源使用和切换性能要求不是非常苛刻的话,使用stackfull的方式会更加方便,代码也更易于维护

具体如何选择,可根据实际使用场景,自己选择哦。。

切换

下面给的tbox的stackless协程切换实例,直观感受下:

static tb_void_t switchtask(tb_lo_coroutine_ref_t coroutine, tb_cpointer_t priv)
{// checktb_size_t* count = (tb_size_t*)priv;// enter coroutinetb_lo_coroutine_enter(coroutine){// loopwhile ((*count)--){// tracetb_trace_i("[coroutine: %p]: %lu", tb_lo_coroutine_self(), *count);// yieldtb_lo_coroutine_yield();}}
}
static tb_void_t test()
{// init schedulertb_lo_scheduler_ref_t scheduler = tb_lo_scheduler_init();if (scheduler){// start coroutinestb_size_t counts[] = {10, 10};tb_lo_coroutine_start(scheduler, switchtask, &counts[0], tb_null);tb_lo_coroutine_start(scheduler, switchtask, &counts[1], tb_null);// run schedulertb_lo_scheduler_loop(scheduler);// exit schedulertb_lo_scheduler_exit(scheduler);}
}

其实整体接口使用跟tbox的那套stackfull接口类似,并没有多少区别,但是相比stackfull还是有些限制的:

1. 目前只能支持在根函数进行协程切换和等待,嵌套协程不支持
2. 协程内部局部变量使用受限

对于限制1,我正在研究中,看看有没有好的实现方案,之前尝试过支持下,后来发现需要按栈结构分级保存每个入口的label地址,这样会占用更多内存,就放弃了。 对于限制2,由于stackless协程函数是需要重入的,因此目前只能在enter()块外部定以一些状态不变的变量,enter()块内部不要使用局部变量

接口设计上,这边采用boost的模式:

// enter coroutine
tb_lo_coroutine_enter(coroutine)
{// yieldtb_lo_coroutine_yield();
}

这样比起protothreads的那种begin()和end(),更加可读和精简,接口也少了一个。。

参数传递

tb_lo_coroutine_start的最后两个参数,专门用来传递关联每个协程的私有数据priv和释放接口free,例如:

typedef struct __tb_xxxx_priv_t
{tb_size_t   member;tb_size_t   others;}tb_xxxx_priv_t;static tb_void_t tb_xxx_free(tb_cpointer_t priv)
{if (priv) tb_free(priv);
}static tb_void_t test()
{tb_xxxx_priv_t* priv = tb_malloc0_type(tb_xxxx_priv_t);if (priv){priv->member = value;}tb_lo_coroutine_start(scheduler, switchtask, priv, tb_xxx_free);
}

上述例子,为协程分配一个私有的数据结构,用于数据状态的维护,解决不能操作局部变量的问题,但是这样写非常繁琐

tbox里面提供了一些辅助接口,用来简化这些流程:


typedef struct __tb_xxxx_priv_t
{tb_size_t   member;tb_size_t   others;}tb_xxxx_priv_t;static tb_void_t test()
{// start coroutine tb_lo_coroutine_start(scheduler, switchtask, tb_lo_coroutine_pass1(tb_xxxx_priv_t, member, value));
}

这个跟之前的代码功能上是等价的,这里利用tb_lo_coroutine_pass1宏接口,自动处理了之前的那些设置流程, 用来快速关联一个私有数据块给新协程。

挂起和恢复

这个跟stackfull的接口用法上也是一样的:

tb_lo_coroutine_enter(coroutine)
{// 挂起当前协程tb_lo_coroutine_suspend();
}// 恢复指定协程(这个可以不在协程函数内部使用,其他地方也可以调用)
tb_lo_coroutine_resume(coroutine);

挂起和恢复跟yield的区别就是,yield后的协程,之后还会被切换回来,但是被挂起的协程,除非调用resume()恢复它,否则永远不会再被执行到。

等待

当然一般,我们不会直接使用suspend()和resume()接口,这两个比较原始,如果需要定时等待,可以使用:

tb_lo_coroutine_enter(coroutine)
{// 等待1stb_lo_coroutine_sleep(1000);
}

来挂起当前协程1s,之后会自动恢复执行,如果要进行io等待,可以使用:

static tb_void_t tb_demo_lo_coroutine_client(tb_lo_coroutine_ref_t coroutine, tb_cpointer_t priv)
{// checktb_demo_lo_client_ref_t client = (tb_demo_lo_client_ref_t)priv;tb_assert(client);// enter coroutinetb_lo_coroutine_enter(coroutine){// read dataclient->size = sizeof(client->data) - 1;while (client->read < client->size){// read itclient->real = tb_socket_recv(client->sock, (tb_byte_t*)client->data + client->read, client->size - client->read);// has data?if (client->real > 0) {client->read += client->real;client->wait = 0;}// no data? wait itelse if (!client->real && !client->wait){// 等待socket数据tb_lo_coroutine_waitio(client->sock, TB_SOCKET_EVENT_RECV, TB_DEMO_TIMEOUT);// 获取等到的io事件client->wait = tb_lo_coroutine_events();tb_assert_and_check_break(client->wait >= 0);}// failed or end?else break;}// tracetb_trace_i("echo: %s", client->data);// exit sockettb_socket_exit(client->sock);}
}

这个跟stackfull模式除了局部变量的区别,其他使用上几乎一样,也是同步模式,但是实际上tbox已经在底层把它放入了poller轮询器中进行等待

在没有数据,调用tb_lo_coroutine_waitio进行socket等待事件后,tbox会自动启用stackless调度器内部的io调度器(默认是不启用的,延迟加载,减少无畏的资源浪费)

然后进行poll切换调度(内部根据不同平台使用epoll, kqueue, poll, 后续还会支持iocp)。

如果有事件到来,会将收到事件的所有协程恢复执行,当然也可以指定等待超时,超时返回或者强行kill中断掉。

tbox中内置了一个stackless版本的http_server,实现也是非常轻量,经测试效率还是非常高的, 整体表现比stackfull的实现更好。

更多stackless接口使用demo,可以参考tbox的源码

信号量和锁

这个就简单讲讲了,使用跟stackfull的类似,例如:


// the lock
static tb_lo_lock_t     g_lock;// enter coroutine
tb_lo_coroutine_enter(coroutine)
{// loopwhile (lock->count--){// enter locktb_lo_lock_enter(&g_lock);// tracetb_trace_i("[coroutine: %p]: enter", tb_lo_coroutine_self());// wait some timetb_lo_coroutine_sleep(1000);// tracetb_trace_i("[coroutine: %p]: leave", tb_lo_coroutine_self());// leave locktb_lo_lock_leave(&g_lock);}
}// init lock
tb_lo_lock_init(&g_lock);// start coroutine
// ..// exit lock
tb_lo_lock_exit(&g_lock);

这里只是举个例子,实际使用中尽量还是别这么直接用全局变量哦。。


个人主页:TBOOX开源工程

转载于:https://my.oschina.net/tboox/blog/799860

tbox新增stackless协程支持相关推荐

  1. C++20 模式下将启用协程支持 CC 11

    GCC 10 在 C++20 模式(std=c++20) 中运行时,默认情况下会启用 C++ 协程.而将于明年发布的 GCC 11 在 C++20 推出时将具有协程功能,到那时,GNU 编译器集合中支 ...

  2. 协程分析之 context 上下文切换

    转载:https://juejin.im/entry/6844903448849154061 协程现在已经不是个新东西了,很多语言都提供了原生支持,也有很多开源的库也提供了协程支持. 最近为了要给tb ...

  3. C/C++协程库libco:微信怎样漂亮地完成异步化改造

    如今,微信拥有月活跃用户8亿. 不可否认,当今的微信后台拥有着强大的并发能力. 不过, 正如罗马并非一日建成:微信的技术也曾经略显稚嫩. 微信诞生于2011年1月,当年用户规模为0.1亿左右:2013 ...

  4. [译]C++ 协程:理解 co_await 运算符

    原文地址:C++ Coroutines: Understanding operator co_await 原文作者:lewissbaker 译文出自:掘金翻译计划 本文永久链接:github.com/ ...

  5. 一文彻底搞懂协程(coroutine)是什么,值得收藏

    什么是协程 我们可以简单的认为:协程就是用户态的线程,但是上下文切换的时机是靠调用方(写代码的开发人员)自身去控制的. 同时,协程和用户态线程非常接近,用户态线程之间的切换不需要陷入内核,但部分操作系 ...

  6. 微信终端自研 C++协程框架的设计与实现

    作者:peterfan,腾讯 WXG 客户端开发工程师 背景 基于跨平台考虑,微信终端很多基础组件使用 C++ 编写,随着业务越来越复杂,传统异步编程模型已经无法满足业务需要.Modern C++ 虽 ...

  7. linux的进程/线程/协程系列5:协程的发展复兴与实现现状

    协程的发展复兴与实现现状 前言 本篇摘要: 1. 协同制的发展史 1.1 协同工作制的提出 1.2 自顶向下,无需协同 1.3 协同式思想的应用 2. 协程的复兴 2.1 高并发带来的问题 2.2 制 ...

  8. pdf 深入理解kotlin协程_协程初探

    Hello,各位朋友,小笨鸟我回来了! 近期学习了Kotlin协程相关的知识,感觉这块技术在项目中的可应用性很大,对项目的开发效率和维护成本有较大的提升.于是就考虑深入研究下相关概念和使用方式,并引入 ...

  9. Python之路 34:并发与并行、锁(GIL、同步锁、死锁与递归锁)、信号量、线程队列、生消模型、进程(基础使用、进程通信、进程池、回调函数)、协程

    内容: 同步锁 死锁.递归锁 信号量和同步对象(暂时了解即可) 队列------生产者和消费者模型 进程(基础使用.进程通信.进程池.回调函数) 协程 一.并发并行与同步异步的概念 1.1.并发和并行 ...

最新文章

  1. 南昌大学计算机控制,南昌大学计算机控制实验报告数/模转换实验..doc
  2. tableau必知必会之如何在Tableau server中实现工作薄的自动刷新
  3. sap内表 table_LINE
  4. spring 测试 事务_Spring陷阱:事务测试被认为是有害的
  5. itext替换pdf中的中文
  6. cannot be null mysql_mysql5.7 column cannot be null-阿里云开发者社区
  7. Web2.0:天使投资与VC的竞技场
  8. 当前只读状态:是_DM数据库的启停以及数据库状态检查
  9. 台电 8G U盘通过量产程序实现USB-CDROM启动
  10. 我的数模之旅(开始)
  11. 如何充分使用腾讯云服务器
  12. php 电子图册制作,想把自己的照片做成电子相册 相册制作软件
  13. 虚拟化服务器端口用万兆,虚拟化升级,千兆变万兆!
  14. Guava:Striped锁
  15. 基于51单片机智能大棚智能花盆浇水浇花灌溉补光散热原理图Proteus仿真
  16. 正则表达式练习题(含答案)
  17. chrome电脑棒 刷linux,其实已经够用:华硕Chromebit电脑棒评测
  18. 2021年安全员-A证考试内容及安全员-A证模拟考试
  19. uniapp的checkbox标签属性
  20. 理解onMeasure

热门文章

  1. mysql 4 基础教程_MySQL基础教程(四):MySQL 管理
  2. 安卓动态.9图拉伸实现方案
  3. 安卓多语言设置,深渊巨坑,适配7.0以上,并且解决因WebView产生的问题
  4. webpack4.x热更新,自动刷新
  5. 自考计算机应用技术实践考核,自考《计算机应用技术》实践环节指导
  6. onbeforeedit和onbeginedit数据不一致_Rrbind.fill|列数不一致的多个数据集“智能”合并,Get!...
  7. 《STL源码剖析》学习--traits
  8. OpenCV的HOG+SVM训练程序注意事项
  9. MyBatis 源码分析 - 缓存原理
  10. Activiti总体框架分析