网络协程编程

一、背景

 为什么需要网络协程?

1、协程/纤程并不是一个新概念
2、大并发、高性能对于服务端的高要求
3、移动设备的快速增长加大了服务端大并发压力
4、Go 语言的兴起将协程带到了一个新的高度

支持协程的编程语言:
1、Go 语言,非常容易支持大并发、高性能
2、Python 语言
3、Erlang 语言
4、Lua 语言
。。。。。。

为什么要设计一套 C/C++ 网络协程库?
1、学习一部门语言的成本要远高于学习一个库
2、C/C++ 程序员多年的经验积累损耗巨大
3、C/C++ 综合运行效率高
二、关于并发
 - 虽已进入多核时代,但服务器的 CPU 核心总是有限的
 - 当进程/线程数越多操作系统的调度算法就越低效
 - TCP长连接及连接池的存在,造成服务端80%以上的连接是空闲的

为支持并发,我们需要采用:
1、多进程模式:支持并发能力非常有限,如 Postfix,Xinetd;
2、多线程模式:比多进程模式有提高,但依然有限,如 Mysql;
3、非阻塞模式:性能高,但编程复杂度极高,如 Nginx,Redis;
4、基于事件的多线程模式:并发度有较大提高,但编程提升依然有限,如 acl 中的 master_threads 服务模式;
三、设计目标
 我们需要一种新的编程模式来满足C/C++程序员:
1、支持大并发、高性能,较低的资源使用率
2、较低的编程复杂度:顺序思维模式
3、适合多数应用场景,提供丰富且简单易用的接口
4、与第三方网络库无缝集成,无需修改第三方库
四、一个简单的协程示例
1、创建协程类似于创建线程
2、支持大并发、高性能
3、顺序性编程方式
4、无需更改第三方库
5、仅使用一个线程资源

 五、协程的调度方式

 1、上下文切换
 通过操作系统提供的 API 完成:getcontext、makecontext、swapcontext、setcontext;
 或 自己通过汇编语言来实现协程运行栈空间的切换
 实现库举例:libtask,boost,libgo, libco,coroutine  等

 2、信号跳转
 通过系统提供的 API 完成:siglongjmp、longjmp、setjmp、sigsetjmp 等
 实现库举例:libmill,st ,coroutine 等

六、协程切换方式


 

七、网络协程调度


 

1、IO事件协程监控所有的IO事件
2、网络协程运行时遇到IO阻塞,则被挂起,其IO句柄由IO事件协程监控
3、IO事件发生时,其绑定的协程被再次唤醒

扫描二维码关注公众号,回复: 353714 查看本文章

八、如何与第三方库无缝集成

1、HOOK IO相关API
读 API:read/readv/recv/recvfrom/recvmsg
写 API:write/writev/send/sendto/sendmsg
其它 API:pipe/popen/pclose/open/close/fcntl
2、HOOK 网络相关API
socket/socketpair/bind/listen/accept/connect
poll/select/epoll_create/epoll_wait/epoll_ctl
gethostbyname/gethostbyname_r

通过 HOOK 系统底层 API,可以实现:
1、直接接管第三方库(如:mysql/http/redis 等库)的网络连接及通信过程
2、直接接管第三方库的域名解析过程
3、将第三方网络阻塞过程协程化,在协程库底层转化为非阻塞过程

将mysql库协程化的例子参见:acl/lib_fiber/samples/mysql

九、为何要 HOOK 很多系统API

1、poll/select 为网络编程中常用系统 API
2、很多第三方网络库用 poll/select 模拟IO超时
3、epoll 在 reactor 类应用(如:聊天)方面比较广泛
4、gethostbyname 在域名解析方面应用广泛
5、listen 需要将监听描述字设为非阻塞模式
6、connect 需要将连接描述字设为非阻塞模式
7、bind/socket/socketpair/。。。为便于将出错号与协程绑定
 

十、基于协程的 errno

因为每个线程中存在大量协程,当某个协程的IO过程出错时,如果实现不同协程之间的 errno 是相互隔离的?
--- 在 Linux 平台下直接 HOOK __errno_location 系统函数
参见:/usr/include/bits/errno.h

extern int *__errno_location (void) __THROW __attribute__ ((__const__));
#define errno (*__errno_location ())

针对进程内全局变量:errno,操作系统将该变量定义为一个函数指针地址,函数内部会通过线程局部变量方式给每一个线程分配一个 error 对象
因此,通过 hook __errno_location 函数,在协程库里给每个协程一个协程局部变量,实现了 errno 全局变量的协程安全性

十一、内存安全检测

配合 valgrind 做内存检测:
- valgrind 与 xxxcontext 的不兼容性
- 需下载 valgrind 开发包,调用 VALGRIND_STACK_REGISTER通知
  valgrind 跳过检测该内存区域
- 检测时在 Makefile 里打开 –DUSE_VALGRIND 编译选项,重新编译 lib_fiber.a

十二、有效使用多核

 每个线程一个独立的协程调度器,通过创建多个线程使用多核
使用 acl master 服务器框架,创建多进程使用多核,每个进程一个协程调度器

多线程示例参见:acl/lib_fiber/samples/redis_threads
多进程示例参见:acl/lib_fiber/samples/master_fiber

十三、协程同步原语



 

基于协程的协程锁:
1、协程互斥锁
2、协程读写锁

十四、协程挂起与唤醒

-- 协程挂起方式
1、主动让出 CPU 控制权
当前运行的协程通过调用 acl_fiber_yield 主动让出 CPU 控制权,协程调度器调用别的协程
2、指定休眠时间
当前运行的协程通过调用 acl_fiber_sleep 使当前协程休眠指定时间
3、IO阻塞被挂起
当前运行的协程等待IO完成时,需要将自身挂起

-- 协程唤醒方式
1、主动 yield 的协程又重新获得 CPU 控制权
2、处于休眠状态的协程时间到达
3、因IO阻塞而被挂起的协程因IO准备好而被唤醒

示例参考:
1、yield 方式:acl/lib_fiber/samples/fiber
2、sleep 方式:acl/lib_fiber/samples/sleep
3、IO 方式:acl/lib_fiber/samples/select

十五、过载保护


 

十六、协程间通信

协程间为什么需要通信?
1、业务逻辑的模块化
2、业务模块的分层设计
3、团队开发的协作性

协程间“通信”的本质:
- 协程间数据的传递通过协程上下文的切换,本质上是协程间的数据交换

协程间“通信”的成本:
1、协程上下文切换
2、内存分配、释放
3、数据拷贝

协程间“通信”方式:
- 支持多对多数据交互



- 协程通信管道支持多对多方式
- 协程间通信通过切换协程上下文及数据交换完成
- 协程间通信时的数据交换支持缓冲模式
- 协程间通信时的数据交换采用随机分配方式

十七、线程间通信

协程模式下为何需要线程间通信?
- 为使用多核,开启多个线程,线程间需要交换数据
- 有些任务需要在线程池里异步完成,结果需要传递给主线程

协程模式下线程间的通信方式:
- 无锁消息队列 + IO 模式

十八、线程间通信


1、生产者/消费者之间优先通过无锁队列进行数据传递
2、当生产者无数据时,消费者通过IO堵塞
3、当消费者堵塞在IO等待新消息时,生产者若有新消息则通过IO通知消费者
4、无锁队列利用率越高,则处理性能越高

十九、应用场景

(一)、问答式应用服务
基于 HTTP 协议的服务应用,诸如:网站
基于 SMTP/POP3/IMAP 协议的服务应用
(二)、生产者 – 消费者类应用服务
如消息队列类应用
(三)、reactor 和 proactor 两种模式的结合
统一的事件引擎监控所有的网络连接,有一个连接就绪时创建协程独立处理
此类应用如聊天服务、游戏服务等无状态的应用服务
(四)、大并发类应用服务
因为通过协程方式,将上层应用的堵塞式在底层转为非阻塞模式,所以非常容易以较低资源支持大并发类应用
如内网的多数应用服务为提高效率都支持连接池模式,需要服务端支持非常大的并发
(五)、网络限流
在协程中可以直接 sleep,非常容易控制网络流量

二十、协程编程注意事项

(一)、协程运行堆栈空间的合理分配
每个协程都需要分配一定的内存空间用于上下文的切换,如果分配大了则会造成内存浪费,分配小了可能造成意外不可恢复的崩溃
一般情况下,每个协程分配32KB ~ 320KB

(二)、协程间需要协作,防止有的忙死,有的饿死
当协程长期占用 CPU 时,应该主动 yield 让出 CPU

(三)、协程内防止有堵塞式操作,以防堵塞当前线程中的所有协程
应通过对业务逻辑模块进行分类,确定不同的协程工作方式,使堵塞操作放在线程池中运行

二十一、参考

 基于协程的简单网络服务:使用 acl 协程编写高并发网络服务

 基于协程的 WEB 服务:使用协程方式编写高并发的 WEB 服务

 协程库:https://github.com/acl-dev/acl/tree/master/lib_fiber

 acl svn:svn checkout svn://svn.code.sf.net/p/acl/code/trunk acl-code

 acl github:https://github.com/acl-dev/acl

 acl 国内镜像:http://git.oschina.net/acl-dev/acl/

 qq 群242722074

 微博:http://weibo.com/zsxxsz/

猜你喜欢

转载自zsxxsz.iteye.com/blog/2312043