我们经常遇到这样一种情况:
服务器A收到客户端的请求:删除该用户的某文件;
A需要向文件服务器转发请求,因此首先要把请求保存起来;
在未来的某个时刻,文件服务器发来了响应;
A再根据响应查找到保存的请求,处理,然后向客户端回复结果:删成功了,或是文件不存在,或是你没有权限等等。
是不是有点复杂?
(这里没考虑文件服务器超时,自己加timer去,不是这里的重点)。
伪代码如下:
- int main()
- {
- while (true)
- {
- network_loop();
- 处理请求;
- }
- }
- void 处理删文件请求(...)
- {
- 保存请求;
- sendTo(文件服务器);
- 回到network_loop();
- }
- void 处理文件服务器的响应(...)
- {
- 查找请求;
- process();
- sendTo(客户端);
- }
协程是应对这种局面的利器。
听起来似乎和线程很像,但有很大的不一样:
协程更轻量级(相当于系统调用的开销而已),协程对内核透明,协程的调度依赖于应用程序,因此是非抢占的。
而线程是操作系统调度,不受我们掌控。
另外,多个协程是在一个线程中串行执行的。
(API请查看windows核心编程的《纤程》一章,以及linux的swapcontext簇函数)
当使用协程时,上面的情况处理变成了这样:
服务器A收到客户端的请求:删除该用户的某文件;
A创建协程1,切换到协程1执行;
该协程向文件服务器转发请求,将自己的唯一标识带在请求中,不等待,立即执行SwitchToCoroutine(main),回到主协程;
在未来的某个时刻,文件服务器发来了响应,主协程根据响应中的唯一标识,找到协程1,切换到协程1执行;
协程1恢复执行,处理请求,向客户端发响应。
听起来是不是也有点复杂?看看代码吧:
- int main()
- {
- while (true)
- {
- network_loop();
- 处理请求,
- 或者是根据请求创建相应的协程;
- }
- }
- 协程1:
- void 处理删文件协程(...)
- {
- 将自己的唯一标识放入请求;
- sendTo(文件服务器);
- Response* rsp = SwitchToCoroutine(main); // 回到服务器主循环;
- process(rsp);
- sendTo(客户端);
- // 处理至此完成,
- // 此处需要主动切换回main协程;
- // 但是可以通过封装API省掉这一步骤
- }
- void 处理文件服务器的响应(Response* rsp)
- {
- Coroutine* pCrt = find_coroutine_by_id();
- SwitchToCoroutine(pCrt, rsp); // 切换到协程1做处理。
- }
是不是要简单的一些?最重要的是,删文件请求的处理,显得一气呵成,代码更加易读。