- 主题:stackoverflow 有个比较 c++ stackful 和 stackless 协程的帖子
关于协程栈。
原来的线程池模式,只有有限个线程,栈空间不是问题。
改成协程模式后,10000个客户端就是10000个协程,每个2M(实际上用过6M)栈的话就是20G。这对于转发器,一个小服务器来说,成本有点高。
我的解决方案如下:
连接到达时,也就是accept之后,分配了context,但是没有栈。直到它的呼叫请求被一个线程抓住,发现这个context没有栈,才给它分配一个栈(向下自增型)。这时,线程一个栈,context一个栈,共两个。
通过makecontext为协程设置一个入口点,swapcontext切换进入工作流程。
此时线程还在抓着这个协程,直到它进入IO,进行yield,才放手。
这个协程,几经转折,完成了任务,结束这次业务请求后,栈被收回。寄存在线程的工作空间。这个线程在接收到空栈的context时可以把这个栈空间转给新的context。线程只寄存一个栈空间。
分配和释放之间会有冲突,如果冲突了,就不分配。线程就直接以同步方式执行这个context。
它所调用的各种IO也都不会执行yield。这个线程就退化成接续模式。在压力测试中发现了这种退化。
【 在 wallyz 的大作中提到:
: 如果说"密钥协商,和登录认证"造成了“IO长时间占用线程”的话,那应该说明的是,之前“接续模式”的程序本身的IO方式有些问题
: 因为SSL_do_handshake本身支持以非阻塞方式进行,主要是适当的处理SL_ERROR_WANT_READ和SSL_ERROR_WANT_WRITE;
: 登录认证就更不用说了,肯定只是一些应用层的消息交互,如果设计没问题,这必然不可能造成IO长时间占用线程
: ...................
--
修改:ylh0315 FROM 221.218.61.*
FROM 221.218.61.*
见84楼,yield之后判断返回状态,如果yield失败,就会自动转成同步模式。
见85楼,如果为context分配栈失败,该context就不是一个协程,它的IO走到这里就会自动转成同步模式。该任务的一切活动不受影响,只不过在大数据包的IO期间死占线程。
测试过从北京呼叫处于国外的管理器,一个64K的包被切成好几段。协程的任务会被调度好几次,非协程的一气呵成,但是期间抓死线程。
【 在 wallyz 的大作中提到: 】
: “如果异步操作出现任何异常,就立即转换为同步模式,不会耽误事。”
: 我对这里也感觉纳闷
: 异步出现非正常情况,除了连接断掉之外,无外乎是EAGAIN/EWOULDBLOCK,也就是read没数据或者write缓冲区满,这时候转成同步模式是不合适的,转成同步模式会阻塞当前线程,这样做和就地poll这个socket没啥两样,都会造成线程被阻塞
: ...................
--
修改:ylh0315 FROM 221.218.61.*
FROM 221.218.61.*
超时问题,同步比较简单,异步要在yield中处理,它的最后一个参数就是timeout。epoll对于单个fd的超时也没有什么好办法。
我的做法是每一步都在context中打时间戳。
记得主线程吗?它除了负责接生,另一个任务就是定期检查各个队列中超时的context。超时后会进行夭折处理。在IO中,yield也是进行TIMEOUT状态的返回,然后整个函数返回TIMEOUT状态。协程自行了断。
while(1) {
do {
FD_ZERO(&efds);
FD_SET(sock, &efds);
//健康检查周期
tm.tv_sec=15;
tm.tv_usec=0;
ret=select(sock+1,&efds,NULL,&efds,&tm);
if(ret==-1) {
ShowLog(1,"select error %s",strerror(errno));
close(sock);
quit(3);
}
if(ret==0) {
check_TCB_timeout();
if(poolchk) poolchk();
}
} while(ret<=0);
后边才是对select出来的进行accept。
超时之后,除了对交易进行夭折处理,还一个重要任务是对连接池的健康管理。对连接故障的判断,进行容错处理。
【 在 wallyz 的大作中提到: 】
: “如果异步操作出现任何异常,就立即转换为同步模式,不会耽误事。”
: 我对这里也感觉纳闷
: 异步出现非正常情况,除了连接断掉之外,无外乎是EAGAIN/EWOULDBLOCK,也就是read没数据或者write缓冲区满,这时候转成同步模式是不合适的,转成同步模式会阻塞当前线程,这样做和就地poll这个socket没啥两样,都会造成线程被阻塞
: ...................
--
修改:ylh0315 FROM 221.218.61.*
FROM 221.218.61.*
嗯。考虑的过timerfd的方案。主要是得解决一个context同时被两个线程处理的情况。
【 在 wallyz 的大作中提到: 】
: 常规非阻塞模型下的超时处理很直接,不是什么难题,
: 每个线程维护一个multimap, key是超时时间戳(当前时间+比如5秒),value指向控制块(或者你说的context),每次io之后更新控制块在multimap时间戳
: 另外起一个timerfd让epoll一起监视,每隔比如100ms timerfd返回,检查multimap,以当前时间戳取upper_bound,从begin开始到upper_bound挨个清理(也就是你说的夭折处理)
: ...................
--
修改:ylh0315 FROM 221.218.61.*
FROM 221.218.61.*
嗯。说的都是linux。大规模交易系统,WINDOWS太不靠谱了。
【 在 hgoldfish 的大作中提到: 】
: timerfd 是 linux 下才能使用的吧。
: 太丑了,如果是 C++,对着那个协程抛出异常就行了。但是需要注意整个 C++ 程序都得搞 RAII, 不然会有资源泄露。
:
--
FROM 221.218.61.*
交易处理都不需要纳秒级,也不用RTOS。
自动控制系统才需要。
交易系统关注的主要指标是:
并发的客户数,响应时间,秒级,亚秒级。交易吞吐量,TPM,每分钟交易量。
【 在 ziqin 的大作中提到: 】
: 因为你在说纳秒级别 如果kernel by pass只省了纳秒级别 那么用cpu跑交易逻辑是没有意义的 因为随便一个大一些的cache miss或者cpu 切片都比这个大
:
: 所有你看见的纳秒级的tick to trade的 都是交易逻辑直接烧在网卡芯片里的
: ...................
--
修改:ylh0315 FROM 221.218.61.*
FROM 221.218.61.*
大家讨论嘛。这贴不是说的协程吗?有说协程怎样用于高频交易的,我就说了这么多。
见谅。
【 在 ziqin 的大作中提到: 】
: 我们在说同一个东西么?做外汇平台的就别在这儿秀了
:
--
FROM 221.218.61.*
你还是没有看完我所有的描述。epoll已经用了,他是多线程协程的调度核心。
系统设计是依据C10K的理论,关于C10K你可以百度。
上万的客户端,是连接到一个线程池系统,这已经是C10K问题的最高境界了。
在线程池系统,由于长IO占用线程(本来就为数不多)的问题,才用到了协程。
stakfull模式,是因为交易系统存在大量第三方软件,无法控制栈的用量。
而每连接一个协程(CPC),又重蹈TPC的覆辙,巨额的栈占用。
于是就有了栈池的想法。
这就是我在本帖描述的逻辑线。
几万个客户端,一人一个协程,他们只在活动期间使用栈。所有协程基于一个线程池(多线程协程)进行运作。
线程池的工作线程负责把客户端的请求转发到服务器并将服务器的应答转发回客户端。
采用连接池连接服务器。连接池用来管理多个交易类型(交易路由处理),每个交易类型有多台服务器,形成负载均衡和容错机制。每台服务器有多个连接(多少核就多少连接)。
每个服务器采用TPC模式,每个连接一个线程。由管理器的连接池确定有限的连接数,不直接面对客户。这就规避了TPC的缺点--无限数量的线程导致资源耗尽。资源管理的问题交给交易管理器,服务器就专心致志的做好业务。
而交易管理器控制着整个服务器系统,并使之达到最高系统交易吞吐量。
所有的描述,与本帖的宗旨,完全扣题。
【 在 ziqin 的大作中提到: 】
: 你想要秀 那我就告诉你 你那个构架就是个垃圾 几万个客户端 你一个连接一个线程?就你这个构架 用不用协程都一样 你的瓶颈是在网络io和数据库io上 你在这儿秀协程甚至都不是正确的使用场景
:
: Epoll IOCP直接解决掉你一半以上的问题 然后另一半 你需要把数据库信息内存化 把数据库和内存结构体同步起来 放弃sql查询 直接读内存结构体
: ...................
--
修改:ylh0315 FROM 221.218.61.*
FROM 221.218.61.*
epoll的超时是整体的超时,管不了个别FD。
【 在 hgoldfish 的大作中提到: 】
: timerfd 要陷入内核态的。这个方案也太差了吧。一般是 epoll() 那边带一个超时参数搞定。不过新的 uring 好像没超时参数了。
:
--
FROM 221.218.61.*
里边几百个fd,谁管得了谁呀。
现在就是不指望epoll。也用不了timerfd。
所有的context就是一个数组,由主线程定时检查每个context的超时情况,对超时的context进行标记,根据其状态做出不同的处理。见88楼。
【 在 hgoldfish 的大作中提到: 】
: 你先算出最近超时的时间,传入那个值。等处理完对应的 fd,再重新算超时时间,重新调用 epoll() 就行了。
:
--
修改:ylh0315 FROM 221.218.61.*
FROM 221.218.61.*