协程

协程,即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程可以在运行期间的某个点上暂停执行,并在恢复运行时从暂停的点上继续执行。 
协程已经被证明是一种非常有用的程序组件,不仅被python、lua、ruby等脚本语言广泛采用,而且被新一代面向多核的编程语言如golang rust-lang等采用作为并发的基本单位。 
协程可以被认为是一种用户空间线程,与传统的线程相比,有2个主要的优点:

  • 与线程不同,协程是自己主动让出CPU,并交付他期望的下一个协程运行,而不是在任何时候都有可能被系统调度打断。因此协程的使用更加清晰易懂,并且多数情况下不需要锁机制。
  • 与线程相比,协程的切换由程序控制,发生在用户空间而非内核空间,因此切换的代价非常小。

网络编程模型

首先来简单回顾一下一些常用的网络编程模型。网络编程模型可以大体的分为同步模型和异步模型两类。

  • 同步模型:

同步模型使用阻塞IO模式,在阻塞IO模式下调用read等IO函数时会阻塞线程直到IO完成或失败。同步模型的典型代表是thread per connection模型,每当阻塞在主线程上的accept调用返回时则创建一个新的线程去服务于新的socket的读/写。这种模型的优点是程序简洁,编写简单;缺点是可伸缩性收到线程数的限制,当连接越来越多时,线程也越来越多,频繁的线程切换会严重拖累性能。

  • 异步模型:

异步模型一般使用非阻塞IO模式,并配合epoll/select/poll等多路复用机制。异步模型可以使一个线程同时服务于多个IO对象。异步模型的典型代表是reactor模型。在reactor模型中,我们将所有要处理的IO事件注册到一个中心的IO多路复用器中(一般为epoll/select/poll),同时主线程阻塞在多路复用器上。一旦有IO事件到来或者就绪,多路复用器返回并将对应的IO事件分发到对应的处理器(即回调函数)中,最后处理器调用read/write函数来进行IO操作。异步模型的特点是性能和可伸缩性比同步模型要好很多,但是其结构复杂,不易于编写和维护。在异步模型中,IO之前的代码(IO任务的提交者)和IO之后的处理代码(回调函数)是割裂开来的,虽然可以通过多线性来提供并发,但是又不得不通过安全队列或加锁的方式来保证逻辑层是单线程的。

协程与网络编程

协程为克服同步模型和异步模型的缺点,并结合他们的优点提供了可能:

首先简单认识一下调度器:一个线程运行一个调度器,可以在一个调度器上创建若干个协程。调度器负责调度这些协程。并且调度器在其内部维护了一个多路复用器(epoll/select/poll)。现在假设我们有3个协程A,B,C分别要进行数次IO操作。这3个协程运行在同一个调度器(线程)的上下文中,并依次使用CPU。协程A首先运行,当它执行到一个IO操作,但该IO操作并没有立即就绪时,A将该IO事件注册到调度器中,并主动放弃CPU。这时调度器将B切换到CPU上开始执行,同样,当它碰到一个IO操作的时候将IO事件注册到调度器中,并主动放弃CPU。调度器将C切换到cpu上开始执行。当所有协程都被“阻塞”后,调度器检查注册的IO事件是否发生或就绪。假设此时协程B注册的IO事件已经就绪,调度器将恢复B的执行,B将从上次放弃CPU的地方接着向下运行。A和C同理。这样,对于某个协程而言,我们采用的是同步的模型;但是对于整个调度器(线程)而言,实际上却是异步的模型。好了,原理说完了,我们来看一个实际的例子,echo server。

echo server

在这个例子中,我们将使用orchid库来编写一个echo server。orchid库是一个构建于boost基础上的 协程/网络IO 库。

echo server首先必须要处理连接事件,我们创建一个协程来专门处理连接事件:

typedef boost::shared_ptr<orchid::socket> socket_ptr;
//处理ACCEPT事件的协程
void handle_accept(orchid::coroutine_handle co) {
try {
orchid::acceptor acceptor(co -> get_scheduler().get_io_service());//构建一个acceptor
acceptor.bind_and_listen("",true);
for(;;) {
socket_ptr sock(new orchid::socket(co -> get_scheduler().get_io_service()));
acceptor.accept(*sock,co);
co -> get_scheduler().spawn(boost::bind(handle_io,_1,sock),orchid::minimum_stack_size());//在调度器上创建一个协程来服务新的socket。
//第一个参数是要创建的协程的main函数,
//第二个参数是要创建的协程的栈的大小。
}
}
catch(boost::system::system_error& e) {
cerr<<e.code()<<" "<<e.what()<<endl;
}
}

在上面的代码中,我们创建了一个acceptor,并让它监听5678端口,然后在"阻塞"等待连接到来,当连接事件到来时,创建一个新的协程来服务新的socket。处理套接字IO的协程如下:

在orchid中,协程的main函数必须满足函数签名void(orchid::coroutine handle),如handle accept所示,其中参数co是协程句柄,代表了当前函数所位于的协程。

//处理SOCKET IO事件的协程
void handle_io(orchid::coroutine_handle co,socket_ptr sock) {
orchid::tcp_ostream out(*sock,co);
orchid::tcp_istream in(*sock,co);
for(std::string str;std::getline(in, str) && out;)
{
out<<str<<endl;
} }

IO处理协程首先在传入的套接字上创建了一个输入流和一个输出流,分别代表了TCP的输入和输出。然后不断地从输入流中读取一行,并输出到输出流当中。当socket上的TCP连接断开时,输入流和输出流的eof标志为会被置位,因此循环结束,协程退出。

orchid可以使用户以流的形式来操作套接字。输入流和输出流分别提供了std::istream和std::ostream的接口;输入流和输出流是带缓冲的,如果用户需要无缓冲的读写socket或者自建缓冲,可以直接调用orchid::socket的read和write函数。但是需要注意这两个函数会抛出boost::system _ error异常来表示错误。

细心的读者可能已经发现,handle io的函数签名并不满足void(orchid::coroutine handle),回到handle accept中,可以发现,实际上我们使用了boost.bind对handle io函数进行了适配,使之符合函数签名的要求。

最后是main函数:

int main() {
orchid::scheduler sche;
sche.spawn(handle_accept,orchid::coroutine::minimum_stack_size());//创建协程
sche.run();
}

在上面这个echo server的例子中,我们采用了一种 coroutine per connection 的编程模型,与传统的 thread per connection 模型一样的简洁清晰,但是整个程序实际上运行在同一线程当中。

由于协程的切换开销远远小于线程,因此我们可以轻易的同时启动上千协程来同时服务上千连接,这是 thread per connection的模型很难做到的;由于整个底层的IO系统实际上是使用boost.asio这种高性能的异步io库来实现的,而且协程切换的开销非常小,所以能够达到很高的性能;在伸缩性方面,可以使用scheduler per cpu的方式,即为每个CPU核心分配一个调度器,有多少个CPU核心就建立多少个调度器。

总结:通过协程,我们可以在保持同步IO模型简洁性的同时,获得近似于异步IO模型的高性能和伸缩性。

基于ASIO的协程与网络编程的更多相关文章

  1. 基于ASIO的协程库orchid简介

    什么是orchid? orchid是一个构建于boost库基础上的C++库,类似于python下的gevent/eventlet,为用户提供基于协程的并发模型. 什么是协程: 协程,即协作式程序,其思 ...

  2. python基于协程的网络库gevent、eventlet

    python网络库也有了基于协程的实现,比较著名的是 gevent.eventlet 它两之间的关系可以参照 Comparing gevent to eventlet, 本文主要简单介绍一下event ...

  3. python 并发编程 基于gevent模块 协程池 实现并发的套接字通信

    基于协程池 实现并发的套接字通信 客户端: from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('12 ...

  4. python教程:使用 async 和 await 协程进行并发编程

    python 一直在进行并发编程的优化, 比较熟知的是使用 thread 模块多线程和 multiprocessing 多进程,后来慢慢引入基于 yield 关键字的协程. 而近几个版本,python ...

  5. 基于TCP/IP协议的C++网络编程(API函数版)

    源代码:http://download.csdn.net/detail/nuptboyzhb/4169959 基于TCP/IP协议的网络编程 定义变量——获得WINSOCK版本——加载WINSOCK库 ...

  6. 基于Socket的低层次Java网络编程

    Socket通讯 网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket.Socket通常用来实现客户方和服务方的连接.Socket是TCP/IP协议的一个十分流 ...

  7. 基于URL的高层次Java网络编程

    一致资源定位器URL URL(Uniform Resource Locator)是一致资源定位器的简称,它表示Internet上某一资源的地址.通过URL我们可以访问Internet上的各种网络资源, ...

  8. python——asyncio模块实现协程、异步编程

    我们都知道,现在的服务器开发对于IO调度的优先级控制权已经不再依靠系统,都希望采用协程的方式实现高效的并发任务,如js.lua等在异步协程方面都做的很强大. Python在3.4版本也加入了协程的概念 ...

  9. python中多线程,多进程,多协程概念及编程上的应用

    1, 多线程 线程是进程的一个实体,是CPU进行调度的最小单位,他是比进程更小能独立运行的基本单位. 线程基本不拥有系统资源,只占用一点运行中的资源(如程序计数器,一组寄存器和栈),但是它可以与同属于 ...

随机推荐

  1. 关于easyUI的datagrid的编辑功能时的问题

    编辑时,如果form中包含了id输入域,会发送一个{id,id}这样的字符串到服务端,因为javascript的function edit(){}逻辑中,已经拿到Id提交了.所以,编辑和添加功能共用的 ...

  2. Java 8新特性探究(八)精简的JRE详解

    http://www.importnew.com/14926.html     首页 所有文章 资讯 Web 架构 基础技术 书籍 教程 Java小组 工具资源 - 导航条 - 首页 所有文章 资讯 ...

  3. iOS之Nib和Xib以及storyboard(故事版)

    本文转发至:http://blog.csdn.net/tonny_guan/article/details/8542789 nib.xib与故事板 如果大家使用过苹果的官方资料,一定会发现某些资料上会 ...

  4. Android非常实用的开源项目框架

    我将文章中所描述的项目都集成在一个apk中,可以直接运行查看效果,2.2以上的机器都可以运行.因为不让直接上传apk文件,我压缩成了zip包 1. Universal-Image-Loader 实现异 ...

  5. Document Classification

    Natural Language Processing with Python Chapter 6.1 由于nltk.FreqDist的排序问题,获取电影文本特征词的代码有些微改动. import n ...

  6. linux devel包 和 非devel包的区别

    devel 包主要是供开发用,至少包括以下2个东西: 1. 头文件 2. 链接库 有的还含有开发文档或演示代码. 以 glib 和 glib-devel 为例: 如果你安装基于 glib 开发的程序, ...

  7. python------unicode字符串转换为其他类型

    问题描述: 一下字符串转换为json类型 {u'src': u'crawl', u'cid': u'Ctengbangguoji', u'datatype': u'ItemBase', u'times ...

  8. 170113、CentOs6.4中安装和配置vsftp简明教程

    一.vsftp安装篇 代码如下: # 安装vsftpdyum -y install vsftpd# 启动service vsftpd start# 开启启动chkconfig vsftpd on 二. ...

  9. C#生成随机验证吗例子

    C#生成随机验证吗例子: public class ValidateCode : IHttpHandler, IRequiresSessionState { HttpContext context; ...

  10. DDD之:Repository仓储模式

    在DDD设计中大家都会使用Repository pattern来获取domain model所需要的数据. 1.什么事Repository? "A Repository mediates b ...