网络协程编程

 一、背景

为什么需要网络协程?

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事件发生时,其绑定的协程被再次唤醒

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

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网络通信与服务器编程框架下载:https://github.com/zhengshuxin/acl

acl网络协程库URL:https://github.com/zhengshuxin/acl/tree/master/lib_fiber

2 9 收藏 评论

关于作者:郑树新

长期从事技术研究及技术管理工作,有近17年工作经历。有两次创业经历,曾任和讯架构师,现任二六三企业通信架构师。 个人主页 · 我的文章 · 6 ·    

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部