免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: codechurch
打印 上一主题 下一主题

[C] 寻求建议,关于异步编程 [复制链接]

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
51 [报告]
发表于 2014-12-26 15:51 |只看该作者
本帖最后由 yulihua49 于 2014-12-26 16:06 编辑
windoze 发表于 2014-12-26 15:37
回复 49# yulihua49

每个fiber 1M的内存用到了什么地方?是保存session数据吗?如果是这样,你用不用fi ...



rsp是每一个线程或fiber的rsp,不管是谁,栈上要留给应用空间,栈下有预留系统空间。
栈上是指rsp高地址的方向,栈下是rsp的低地址方向。有资料说是系统异常处理用,我不是太肯定,就笼统的说系统用,反正2K不够,会冲堆(我是malloc的),4K可以。
所以你给用户配栈,4K是不够的,就算应用只用4K,你就要配8K。linux的例子是16K。

每个栈的总空间是1M-2M,包括栈上和栈下,当然是栈上为主,一个压缩器就用530K(局部变量),我留1M不过分吧?

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
52 [报告]
发表于 2014-12-26 16:31 |只看该作者
回复 51# yulihua49

stack本身没什么特别的,就只是一块内存空间而已,这块空间无论分配到什么地方都不重要,只要你在适当的时候把rsp指向这块空间中适当的地方整个程序就能顺利运转起来。rsp“之上”就是你没用到的stack,rsp“之下”就不一定是什么了,比如在你的程序里用malloc分配了stack,那这个stack实际就在进程的heap里,和其它所有在heap里的数据混在一起。

但分配方法很重要,你用malloc当然会出问题,因为malloc分配的空间,无论大小都已经commit了,也就是说系统已经实际分配了物理内存。
mmap则不同,即使你用mmap“分配”了1G内存,但在你实际使用之前都不会有任何的物理内存分配,即使在开始使用之后,mmap的内存区域也只会分配你用到的大小的物理内存,当然它的行为和malloc不太一样,最小内存空间的粒度是系统的page size,在X86/64上是4K。所以即便是你现在的程序,只要将malloc改为mmap,整个程序的内存占用就会好很多。
另外还有个不要用malloc的理由,stack overflow是一种常见的错误,而你在heap里用malloc分配stack没办法为这种问题提供任何保护,但mmap可以将分配的内存前后两页指定为不可读不可写,这样当程序遇到了stack overflow的时候就会发生seg fault,不会静悄悄的被人黑掉。

split stack是GCC实现的一种特殊的技术,它可以让程序在需要时自动扩展stack,在不需要时释放已分配的stack空间,具体的细节可以去看 https://gcc.gnu.org/wiki/SplitStacks  。但重点是,你可以只预留很小的stack给thread/fiber,这个stack会在必要时自动扩展,不需要你改程序。

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
53 [报告]
发表于 2014-12-26 19:59 |只看该作者
本帖最后由 yulihua49 于 2014-12-26 20:02 编辑
windoze 发表于 2014-12-26 16:31
回复 51# yulihua49

stack本身没什么特别的,就只是一块内存空间而已,这块空间无论分配到什么地方都不 ...

学习,我可以这样考虑。

栈下,的确,不知是什么。但是,可以肯定的是,有人在不断使用它,因为那些内容在不断改变。

论坛徽章:
0
54 [报告]
发表于 2014-12-30 11:53 |只看该作者
回复 40# yulihua49

栈用于保存状态,一个业务没有完成,栈如何回收?

线程数量有限,业务数量远超线程数量,所以,当fiber(承载一个业务)得到I/O事件后,fiber获取一个线程开始执行。


比如, aio_send(  callback );  (伪码)发起异步I/O,I/O由系统完成,调用callback回调函数,在这个callback函数里,继续fiber的工作。


在fiber未完成业务之前,栈无法回收。

比如,一个业务函数:

   
void  biz(...)
{
      aiop   op;
      char buff[100];
      int len;

     
      op = a_recv(buff, 100);

      async_wait(op);  /// *

      op = a_send("ok", 2);
   
      async_wait(op); /// **
}


在*和**处,fiber放弃线程,进入“等待I/O完成”的状态,此时,执行线程(可能是在回调,也可能是在线程池)的函数返回。
此时,fiber栈必须保留,因为异步I/O要用到那些内存,比如buff[100]这块内存,被异步a_recv函数填充。

     
我认为,异步过程(如这个biz函数)编程时注意一下,不要使用过多栈,如果要用,就用malloc分配。就是使用上注意一下就可以了。

我现在只用 4K~16K之间。



   

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
55 [报告]
发表于 2014-12-30 15:57 |只看该作者
本帖最后由 yulihua49 于 2014-12-30 16:11 编辑
codechurch 发表于 2014-12-30 11:53
回复 40# yulihua49

栈用于保存状态,一个业务没有完成,栈如何回收?

作为服务器,业务,是由一个一个的服务请求实现的。以铁略售票为例,成千上万的客户端连着,交易是一笔一笔做的,每个交易又是一系列的服务请求组成。
这些成千上万的客户端,长期连着,我们这时并不给他分配内存(栈),这事被推迟到服务请求发生后,当一个请求完成时就收回了他的内存。这样我们实际上只需要2*线程数的栈。就是说,真正活动了(epoll激活后),才需要栈。
我们并不完全是malloc/free来完成分配和释放。每个线程有一个栈池,就一个单元,在tc->uc_stack里。每次是get/release方式。这样没有时间开销。
只有,极少发生,IO未完成,就是挂起,他的栈不回收。直到全部任务完成才回收。此时与之并行的任务就要自己分配内存了。见38楼的程序。

论坛徽章:
0
56 [报告]
发表于 2014-12-30 16:07 |只看该作者
回复 55# yulihua49

如果不保存状态,用协程有什么好处?

我用协程的唯一动机是,我可以将一个多次交互I/O的过程,从自动机写法转换为流程式写法。

   

论坛徽章:
0
57 [报告]
发表于 2014-12-30 16:08 |只看该作者
回复 55# yulihua49


你不给一个业务流程分配栈,那么你的业务就不能写在一个函数里。

那么,协程的好处体现在什么地方?


   

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
58 [报告]
发表于 2014-12-30 16:12 |只看该作者
本帖最后由 yulihua49 于 2014-12-30 16:49 编辑
codechurch 发表于 2014-12-30 16:07
回复 55# yulihua49

如果不保存状态,用协程有什么好处?

我的动机与你一模一样。只是,在需要时就会给他,不需要时就会收回。
这时,我需要的栈数,2*线程数+IO挂起的任务数。
看起来管理逻辑比较复杂,实际上实现起来非常简单,认真研究一下38楼,那是个成功的例子,完全实现了这个复杂的逻辑。

透明的同步IO的例子在39楼,当你调用RecvNet()或SendNet()时,你就感觉是同步阻塞的调用,完全不知道内里已经转换成异步,而且,可能中途yield过。
真正异步挂起的情况是很少发生的,万一挂起的任务太多,内存不够,也不会耽误事,那就不分配了,直接使用线程栈,但是就没有AIO功能了。

论坛徽章:
0
59 [报告]
发表于 2014-12-30 16:15 |只看该作者
回复 58# yulihua49

问题是,栈不能这样回收啊,前面已经讨论过了。

你回收正在使用的栈,指向此栈上内存的指针就无效了,原生语言无法解决这个问题。


   

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
60 [报告]
发表于 2014-12-30 16:20 |只看该作者
本帖最后由 yulihua49 于 2014-12-30 16:53 编辑
codechurch 发表于 2014-12-30 16:15
回复 58# yulihua49

问题是,栈不能这样回收啊,前面已经讨论过了。

肯定是不使用了我才回收的。
do_work()彻底返回了(一个服务请求完成了),就是AIO_flg==0了,我才回收。否则我没有回收啊!
AIO_总是0,除非,do_event(),挂起AIO,yield了它才非0,epoll之后会再次setcontext,这过程,资源一直是给他的,没有回收。

这个方案,既不费内存也不费时间,管理也不复杂,一共也就是100多行的代码。

想出这个流程的确挺费心思的,中途还经历了彻底失败。不过现在成功了,发出来共享一下。

跳栈,只发生在调度器里,应用程序里不会发生跳栈,所以先前的弊端不存在了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP