- 主题:stackoverflow 有个比较 c++ stackful 和 stackless 协程的帖子
我谨慎怀疑你的内存是不是被以某种不正常的方式耗光了
听起来一个客户端是一个较短时间的连接,一个客户端引发的负载不会很高,只有10000个客户端,不至于把8台16核的机器搞得登录都不能登陆吧
【 在 ylh0315 的大作中提到: 】
: 做过一个压力测试,服务器8台各16核,
: 管理器12核,12线程。模拟了10000个客户端,把8台服务器干到了100%,管理器也干到了85%的CPU利用率。
: 多线程协程自己写的,用的makecontext那一组函数,stackfull。自己造轮子,没有哪个现成的适用。
: ...................
--
FROM 113.120.108.*
我的看法:
如果要提供编程框架给别人用,为了让别人用起来像阻塞式变成一样方便,那这时候可能需要考虑用协程的方式实现你的框架
如果是为了自己用,完全可以自己用一个控制块来对应一个连接或者请求,到底对应什么取决于实际业务逻辑,实现一个状态机,然后直接用传统的方式epoll或者封装过的libev或者asio,来写代码,连接或者请求的状态变化在控制块里面记录。然后根据不同的状态和消息驱动运转状态机,相比攒一个协程的实现方式,这种方式既直观又省力
【 在 hgoldfish 的大作中提到: 】
: 这个是基于处理 socket 连接,源于 unix 系统可以由多个线程抢同一个 listening socket. 这个机制受系统的影响比较大,有一定的可能性几个线程抢了所有的 accepted socket, 而大部分线程饿死。
: 所以如果协程是处理其它事情,或者想要更好的负载均衡效果,最好是搞自定义的队列。
:
--
FROM 113.120.108.*
我有点纳闷,如果“IO会长时间占用线程”,这里我理解的IO肯定是非阻塞式的IO,那非阻塞IO怎么会长时间占用线程呢?
即使要发的数据包特别长,在socket buffer未满的情况下,本质上也还是内存拷贝(甚至零拷贝),也是不会长时间占用线程的
而如果socket buffer满了,遇见EAGAIN,这时候不应该就地重试send或者sleep或者就地poll,而是应该放弃这次send,转而处理别的连接,等待之前的socket的EPOLLOUT事件之后再重新send,所以没有道理会有一个连接长时间占用线程
而如果是read,你可以level trigger选择read一些,也可以选择edge trigger全部读完或者level trigger也可以选择全部读完,然后一个连接一个连接的过;无论哪种,对所有的连接都是机会均等的,
所以我很纳闷“IO会长时间占用线程”是如何发生的?
另一个角度,相比于你说的接续模式,你协程的实现本质上只是你的应用逻辑的组织形式和调用方式发生了变化,并没有改变你的IO模型(如果你之前就是用的非阻塞IO的话),也就是说从你的IO操作期间,比如调用send,到send返回,这期间你的协程所在的线程不能被别的协程抢占,read同理;那如果以前用接续模式会出现“IO会长时间占用线程”,那换成协程的方式,“IO会长时间占用线程”又是如何凭空消失的?
难道你在非协程的时候,选择一次读完,协程的时候选择读一些,造成了最后的结果差异?按理说一次读完的IO效率会更好才对;或者你在IO读完之后回调应用层逻辑的时候,数据的长短造成了应用层逻辑的行为有显著差异?
总之,对于协程解决了“IO会长时间占用线程”这个结论,我是很纳闷的
【 在 ylh0315 的大作中提到: 】
: 说的非常对。
: 交易管理器的设计,原来是没有协程的。采用接续模式。就是把任务划分成一个个的部分(我管他叫珠子),由IO(epoll,链子)接续起来。这样也能解决80~90%的问题。
: 但是有两个问题:
: ...................
--
FROM 113.120.108.*
如果说"密钥协商,和登录认证"造成了“IO长时间占用线程”的话,那应该说明的是,之前“接续模式”的程序本身的IO方式有些问题
因为SSL_do_handshake本身支持以非阻塞方式进行,主要是适当的处理SL_ERROR_WANT_READ和SSL_ERROR_WANT_WRITE;
登录认证就更不用说了,肯定只是一些应用层的消息交互,如果设计没问题,这必然不可能造成IO长时间占用线程
实际上,我理解中stackful的协程,内存占用天然的会比你所讲的接续模式或者stackless协程多,因为后二者只需要把必要的数据保留在控制块或者协程体中,而stackful本质上是把stack里的东西全部保留住直到下一次被调度(等待的事件到来),stack里面的东西必然有尚未弹出的非必要数据,这些非必要数据一起被保留在内存里,所以内存占用必然会多
【 在 ylh0315 的大作中提到: 】
: 说的非常对。
: 交易管理器的设计,原来是没有协程的。采用接续模式。就是把任务划分成一个个的部分(我管他叫珠子),由IO(epoll,链子)接续起来。这样也能解决80~90%的问题。
: 但是有两个问题:
: ...................
--
FROM 113.120.108.*
“如果异步操作出现任何异常,就立即转换为同步模式,不会耽误事。”
我对这里也感觉纳闷
异步出现非正常情况,除了连接断掉之外,无外乎是EAGAIN/EWOULDBLOCK,也就是read没数据或者write缓冲区满,这时候转成同步模式是不合适的,转成同步模式会阻塞当前线程,这样做和就地poll这个socket没啥两样,都会造成线程被阻塞
【 在 ylh0315 的大作中提到: 】
: 例如:int RecvNet(int socket,char *buf,int n,int timeout)
: 接收,整个系统,包括客户端,管理器,服务器,及各种模式(多进程,多线程,线程池),都是通过这个函数进行的。
: 它基本就是read。多了个TIMEOUT。它原来是阻塞模式循环接收。
: ...................
--
FROM 113.120.108.*
常规非阻塞模型下的超时处理很直接,不是什么难题,
每个线程维护一个multimap, key是超时时间戳(当前时间+比如5秒),value指向控制块(或者你说的context),每次io之后更新控制块在multimap时间戳
另外起一个timerfd让epoll一起监视,每隔比如100ms timerfd返回,检查multimap,以当前时间戳取upper_bound,从begin开始到upper_bound挨个清理(也就是你说的夭折处理)
【 在 ylh0315 的大作中提到: 】
: 超时问题,同步比较简单,异步要在yield中处理,它的最后一个参数就是timeout。epoll对于单个fd的超时也没有什么好办法。
: 我的做法是每一步都在context中打时间戳。
: 记得主线程吗?它除了负责接生,另一个任务就是定期检查各个队列中超时的context。超时后会进行夭折处理。在IO中,yield也是进行TIMEOUT状态的返回,然后整个函数返回TIMEOUT状态。协程自行了断。
: ...................
--
FROM 113.120.108.*
每个线程只有一个timerfd,每个timerfd几百毫秒产生一次EPOLLOUT,代价不会很大吧
而让epoll带一个小的超时时间提早返回,也一样得从内核态往用户态转,然后还是一样得重新回到epoll wait,
让epoll带一个小的超时时间提早返回和用timerfd,好像是个朝三暮四的问题吧
【 在 hgoldfish 的大作中提到: 】
: timerfd 要陷入内核态的。这个方案也太差了吧。一般是 epoll() 那边带一个超时参数搞定。不过新的 uring 好像没超时参数了。
:
--
FROM 113.120.108.*
如果连接(协程)数量很多的话,每个都配一个deadline timer或者steady_timer,代价似乎很大吧
其实批量对齐处理足够了,比如100ms或者50ms一次批量对齐处理超时,这样的代价可能是有点协程超时处理稍稍晚了一点点,但对于绝大部分(除了超高实时性要求的)系统来说,精确度已经足够了
何况deadline timer本来也不保证能非常精确的返回,有时候会早一点有时候会晚一点
【 在 ziqin 的大作中提到: 】
: 每个协程配一个asio deadline timer 不过看他对他那个构架这么津津乐道的样子 估计没用过asio框架
:
: 他这点需求和吞吐量 对asio根本不是个事
: ...................
--
FROM 113.120.108.*
我的朴素理解:
协程就是内嵌数据、自带可更新的状态、可根据当前状态和数据恢复/继续执行的一个东西
至于是用栈,还是用另外的某种数据结构(比如一个很另类的类)来实现这个东西,理论上都是可以的
关于协程的恢复执行,可能是相对底层一点的协程调度程序基于IO触发的,也可能是用户手动触发的比如你可以手动的让一个协程恢复/继续执行。前者的典型表现是go读写socket,后者典型的表现就是Python的generator。其实本质上,协程的恢复执行或者调度都是人工的,只不过这个人工可能是掌管真正的IO的协程调度程序,也可能真的是你的应用程序
但是协程似乎并不能很优雅的处理这样一个问题:同时发N个消息出去,然后等待它们的响应,等所有的响应都到之后,再做下一个动作。用协程似乎还是会沦落到要写一个switch来处理响应
【 在 hgoldfish 的大作中提到: 】
: 太有用了啊。比如 python 的 os.walk() 就是典型的应用。用迭代器以后,不再需要把整个 list/map 传递给上层调用者,经常能够省掉大量内存。
: 再来一个编译原理的应用,解析 token:
:
https://github.com/hgoldfish/gsion_script/blob/master/gsion#L78: ...................
--
FROM 113.120.108.*
提醒一下,五个响应回来的顺序不一定
【 在 ylh0315 的大作中提到: 】
: 最后一段。
: 一个协程发一个消息出去,然后等结果就好了。
: 都是自动处理的。
: ...................
--
FROM 113.120.108.*