当前位置: 首页 > 工具软件 > orchid > 使用案例 >

基于ASIO的协程库orchid简介

左丘昕
2023-12-01

什么是orchid?

orchid是一个构建于boost库基础上的C++库,类似于python下的gevent/eventlet,为用户提供基于协程的并发模型。

什么是协程:

协程,即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程在控制离开时暂停执行,当控制再次进入时只能从离开的位置继续执行。 协程已经被证明是一种非常有用的程序组件,不仅被python、lua、ruby等脚本语言广泛采用,而且被新一代面向多核的编程语言如golang rust-lang等采用作为并发的基本单位。

协程可以被认为是一种用户空间线程,与传统的抢占式线程相比,有2个主要的优点:

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

green化

术语“green化”来自于python下著名的协程库greenlet,指改造IO对象以能和协程配合。某种意义上,协程与线程的关系类似与线程与进程的关系,多个协程会在同一个线程的上下文之中运行。因此,当出现IO操作的时候,为了能够与协程相互配合,只阻塞当前协程而非整个线程,需要将io对象“green化”。目前orchid提供的green化的io对象包括:

  • tcp socket(还不支持udp)
  • descriptor(目前仅支持非文件类型文件描述符,如管道和标准输入/输出,文件类型的支持会在以后版本添加)
  • timer (定时器)
  • signal (信号)

chan:协程间通信

chan这个概念引用自golang的chan。每个协程是一个独立的执行单元,为了能够方便协程之间的通信/同步,orchid提供了chan这种机制。chan本质上是一个阻塞消息队列,后面我们将看到,chan不仅可以用于同一个调度器上的协程之间的通信,而且可以用于不同调度器上的协程之间的通信。

多核

建议使用的scheduler per cpu的的模型来支持多核的机器,即为每个CPU核心分配一个调度器,有多少核心就创建多少个调度器。不同调度器的协程之间也可以通过chan来通信。协程应该被创建在那个调度器里由用户自己决定。

预备知识

orchid的实现严重依赖于boost,依赖的主要子库包括:boost.context boost.asio boost.iostreams shared_ptr boost.bind 等等。如果用户对这些子库,尤其是boost.asio和boost.bind、shared_ptr具有一定的了解的话,会更加有利于了解和使用orchid。当然如果不了解也没有关系,本文会在后面的例子中对涉及的相关知识进行简单的介绍。

编译与安装

orchid 本身是个只包含头文件的模板库,拷贝到指定的目录即可,但是orchid依赖的boost库需要编译。而且在使用orchid的时需要链接 boost_context boost_iostreams boost_system boost_thread 等子库(参见unit_test里的CMakeLists.txt)。

boost需要采用最新的svn里的版本,因为1.52及以下版本缺少boost.atomic等子库。

MAC OS

git clone https://github.com/ryppl/boost-svn
cd boost-svn
./bootstrap.sh
./b2 toolset=clang cxxflags="-arch x86_64" linkflags="-arch x86_64" install
cd ..
git clone https://github.com/ioriiod0/orchid.git
cd orchid
cp -r orchid <安装路径>

LINUX

git clone https://github.com/ryppl/boost-svn
cd boost-svn
./bootstrap.sh
./b2 install
cd ..
git clone https://github.com/ioriiod0/orchid.git
cd orchid
cp -r orchid <安装路径>

例子

第一个栗子:一个复杂一些的hello world

国际惯例,让我们从hello world开始。在这个例子中,我们将看到如何创建和执行一个协程。

//函数签名为 void(orchid::coroutine_handle) 的函数
void f(orchid::coroutine_handle co) {
    std::cout<<"f:hello world"<<std::endl;
}

//函数签名为 void(orchid::coroutine_handle,const char*) 的函数
void f1(orchid::coroutine_handle co,const char* str) {
    std::cout<<str<<std::endl;
}

void stop(orchid::coroutine_handle co) {
    co -> get_scheduler().stop();
}

int main(int argc,char* argv[]) {
    orchid::scheduler sche;
    sche.spawn(f,orchid::coroutine::minimum_stack_size());
    sche.spawn(boost::bind(f1,_1,"f1:hello world"),orchid::coroutine::default_stack_size());
    sche.spawn(stop);
    sche.run();
    std::cout<<"done!"<<std::endl;
}

程序的输出:

f:hello world
f1:hello world
done!

在这个例子中,我们首先声明一个调度器sche,然后调用sche的spawn方法以2种方式创建了2个协程来输出hello world,最后调用调度器的run方法来执行整个程序。当程序执行时,2个协程依次被执行。需要注意的是,在调用run方法之前,被创建的协助程序并不会被执行,只有调用了run方法之后,被创建的协程才会被调度执行。 调用run方法的线程会被阻塞,直到调度器的stop方法被调用为止。 (实际上,在这个例子中我们并没有对std::cout进行green化,因此每次调用std::cout的时候,整个调度器/线程都会被阻塞,在后面的介绍中我们将看到如何将IO对象green化)。

spawn方法有2个参数:

template <typename F>
void spawn(const F& func,std::size_t stack_size = coroutine_type::default_stack_size())

第一个参数func是协程被执行时所执行的函数。可以认为是协程的main函数。func可以是任何符合函数签名

void(orchid::coroutine_handle)

的函数或函数对象,如上面例子中的f。其中,orchid::coroutine_handle是协程的句柄,代表了func本身所处的协程。 往往要完成协程所执行的任务,仅仅有orchid::coroutine_handle是不够的,比如函数f1,f1需要额外传入一个const char* 的参数,对于这种函数,我们可以通过boost.bind、std::bind(tr1或者C++11)、或者lambda表达式(c++11)将他们适配成要求的函数类型。如上例中的:

sche.spawn(boost::bind(f1,_1,"f1:hello world"),orchid::coroutine::default_stack_size())

boost::bind将f1从void(orchid::coroutine,const char*)适配成了void(orchid::coroutine_handle)类型。其中_1是占位符,表示空出f1的第一个参数,而f1的第二个参数由"f1:hello world"填充。在后面的例子中将会大量出现这种用法,如果用户对boost.bind并不是很清楚,可以先阅读一下boost文档中的相关章节。

第二个参数指定了协程栈空间的大小,orchid::coroutine提供了3个相关的函数:

std::size_t default_stack_size();//默认的栈大小,一般为16个内存页
std::size_t minimum_stack_size();//栈的最小值,一般为2个内存页面
std::size_t maximum_stack_size();//栈的最大值,一般与进程的栈的最大值(软限制)相同。

用户可以根据自己的情况指定所需栈空间的大小。当协程使用的栈空间超过了提供的栈空间的大小的时候,程序会异常结束, orchid并不会自动增加栈空间的大小,所以用户必须预估出足够的栈空间。

这个hello world的例子过于简单,下面我们将通过创建一个echo server的客户端和服务器端来进一步了解orchid。

第二个栗子:echo server

第二个栗子,让我们从网络编程届的hello world:echo server开始。echo server首先必须要处理连接事件,在orchid中,我们创建一个协程来专门处理连接事件:

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.bind_and_listen("5678",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());
        }
    }
    catch(boost::system::system_error& e) {
        cerr<<e.code()<<" "<<e.what()<<endl;
    }
}

在上面的代码中,我们创建了一个green化的acceptor,并让它监听5678端口,然后在"阻塞"等待连接到来,当连接事件发生时,创建一个新的协程来服务新得到的socket。green化的socket被包裹在智能指针中以参数形式传递给处理socket io事件的协程。处理套接字IO的协程如下:

//处理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;
    }

}

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

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

最后是main函数:

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

然后我们来看客户端的代码,在客户端中,我们创建100个并发的TCP连接不断的向echo server发送hello world。

首先是处理socket io的协程:

void handle_io(orchid::coroutine_handle co) {
    orchid::descriptor stdout(co -> get_scheduler().get_io_service(),STDOUT_FILENO);
    orchid::socket sock_(co -> get_scheduler().get_io_service());
    try {
        sock_.connect("127.0.0.1","5678",co);
        orchid::tcp_istream in(sock_,co);
        orchid::tcp_ostream out(sock_,co);
        orchid::descriptor_ostream console(stdout,co);
        out << "hello world !!!!" <<endl;
        for (string str;std::getline(in,str);) {
            console << str << endl;
            out << "hello world !!!!" <<endl;
        }
    } catch (const boost::system::system_error& e) {
        cerr<<e.code()<<" "<<e.what()<<endl;
    }
}

处理socket io的协程分别创建了一个green化的socket和一个green话的标准输出,然后连接到echo server上,不断执行 输出 -> 接收 -> 打印 这个流程。

为了能够从外部打断client的执行,我们还需要一个协程来处理中断信号,这样我们就可以用ctrl+c来正确的中断程序的执行:

void handle_sig(orchid::coroutine_handle co) {
    orchid::signal sig(co -> get_scheduler().get_io_service());
    try {
        sig.add(SIGINT);
        sig.add(SIGTERM);
        sig.wait(co);
        co->get_scheduler().stop();

    } catch (const boost::system::system_error& e) {
        cerr<<e.code()<<" "<<e.what()<<endl;
    }
}

在这个协程中,协程“阻塞”在SIGINT 和 SIGTERM信号上,当信号发生时,调用调度器的stop方法来中断程序的执行,并安全的回收资源。

int main() {
    orchid::scheduler sche;
    sche.spawn(handle_sig,orchid::coroutine::minimum_stack_size());
    for (int i=0;i<100;++i) {
        sche.spawn(handle_io);
    }
    sche.run();
}

在客户端的main函数中,我们创建100个协程,同时向服务器发送hello world。

在上面这个echo server的例子中,我们采用了一种 coroutine per connection 的编程模型,与传统的 thread per connection 模型一样的简洁清晰,但是整个程序实际上运行在同一线程当中。 由于协程的切换和内存开销远远小于线程,因此我们可以轻易的同时启动上千协程来同时服务上千连接,这是 thread per connection的模型很难做到的; 在性能方面,整个green化的IO系统实际上是使用boost.asio这种高性能的异步io库实现的,与原始的asio相比,orchid的性能损耗非常小,性能基本持平。 因此通过orchid,我们可以在保持同步IO模型简洁性的同时,获得近似于异步IO模型的高性能。

第三个栗子:生产者-消费者

在这个例子中,我们将主要介绍orchid提供的协程间的通信机制:chan。chan这个概念引用自golang的chan。chan表现为一个阻塞消息队列。 orchid提供的chan只支持 单生产者-单消费者 和 多生产者-单消费者 这两种模型(在其他模型,如多生产者-多消费者中,也可以工作,单可能会出现某些消费者饿死的现象)。

在下面的例子中,代码orchid::chan < int > ch(10) 表示创建一个大小为10,装载类型为int的chan。 chan 有3个重要的接口:

// 向chan中发送一个对象。t为要发送的对象,co为当前协程的协程句柄。
// 当队列为空时,co会阻塞,当队列不在为空时,阻塞的协程会被唤醒;
// 发送成功返回true,否则false。
bool send(const U& t,coroutine_pointer co);

// 从chan中接收一对象。
// 当队列满时,co会阻塞,当队列不再满时,阻塞的协程会被唤醒。
// 接收成功返回true,否则false。
bool recv(U& t,coroutine_pointer co);

// 关闭一个chan,当chan关闭后,再调用send和recv都会直接返回false;并且所有阻塞在chan中的协程都会被唤醒
// 被唤醒的协程中 send/recv 返回false。
void close();

下面是一个简单的生产者和消费者的例子:

//生产者,不断发送自己的ID给消费者
void sender(orchid::coroutine_handle co,int id,orchid::chan<int>& ch) {
    for (;;) {
        ch.send(id,co);
    }
}
//消费者,不断接收生产者发送的ID并打印ID.
void receiver(orchid::coroutine_handle co,orchid::chan<int>& ch) {
    orchid::descriptor stdout(co -> get_scheduler().get_io_service(),STDOUT_FILENO);
    orchid::descriptor_ostream console(stdout,co);
    int id;
    for (;;) {
        ch.recv(id,co);
        console<<"receiver receive: "<<id<<std::endl;
    }
}

//生产者和消费者运行在同一个调度器中。
void test_one_scheduler() {
    orchid::scheduler sche;
    orchid::chan<int> ch(10);
    for (int i=0;i<100;++i) {
        sche.spawn(boost::bind(sender,_1,i,boost::ref(ch)));
    }
    sche.spawn(boost::bind(receiver,_1,boost::ref(ch)));
    sche.run();
}

//生产者和消费者运行再不同的调度器中。
void test_scheduler_group() {
    orchid::scheduler_group group(2);
    orchid::chan<int> ch(10);
    for (int i=0;i<100;++i) {
        group[i%2].spawn(boost::bind(sender,_1,i,boost::ref(ch)));
    }
    group[0].spawn(boost::bind(receiver,_1,boost::ref(ch)));
    group.run();
}

通过scheduler_group类我们可以方便的创建一组调度器,每个调度器运行在一个单独的线程中。可以通过下标来访问某个调度器;通过调用其run方法同时启动多个调度器;通过调用其stop方法,同时停止多个调度器。

第四个栗子:chat server

这次我们来一个复杂一些的例子:chat server 和 chat client。从这个例子中我们将看到一些有用的技巧,比如如何使用boost::shared_from_this来管理协程间共享对象的生命周期;如何利用boost.variant在一个chan中接收多种类型消息。

先从较为简单的chat clent开始:在chat client中我们将创建两个协程,一个不断从本机的标准输入读取输入,然后发送到chat server;另一个则不断从chat server接受消息并发送到本机的标准输出上。

const static std::size_t STACK_SIZE = 64*1024;

//客户端类
class chat_client {
public:
    chat_client(const string& ip,const string& port)
        :sche_(),
        stdin_(sche_.get_io_service(),STDIN_FILENO),//green化的标准输入
        stdout_(sche_.get_io_service(),STDOUT_FILENO),//green化的标准输出
        sock_(sche_.get_io_service()),
        is_logined_(false),ip_(ip),port_(port) {

    }
    ~chat_client() {

    }
public:
    //创建协程并启动调度器
    void run() {
        sche_.spawn(boost::bind(&chat_client::handle_console,this,_1),STACK_SIZE);
        sche_.run();
    }
    //停止调度器
    void stop() {
        sche_.stop();
    }

private:
    // 不断从chat server接收消息并打印到标准输出上。
    void receive_msg(orchid::coroutine_handle co) {
        string str;
        orchid::descriptor_ostream out(stdout_,co);
        orchid::tcp_istream in(sock_,co);
        for (string str;std::getline(in, str);) {
            out<<str<<endl;
        }
    }

    //不断从标准输入接收用户输入,并处理用户输入,发送消息chat server。
    //登陆用 /l username
    //发送消息用 /s xxxxxxx
    //退出用 /q
    void handle_console(orchid::coroutine_handle co) {
        orchid::descriptor_istream in(stdin_,co);
        orchid::tcp_ostream out(sock_,co);
        //首先连接chat server
        try {
            sock_.connect(ip_,port_,co);
        } catch (boost::system::system_error& e) {
            cerr<<e.code()<<" "<<e.what()<<endl;
            return;
        }
        //连接成功则启动接受消息的协程。
        sche_.spawn(boost::bind(&chat_client::receive_msg,this,_1), STACK_SIZE);
        //不断读取标准输入并进行处理。
        for(string str;std::getline(in,str);) {
            if(str.empty()) continue;
            // 退出 “/q”
            if(str.size() >= 2 && str[0] == '/' && str[1] == 'q') { 
                sock_.close();
                user_.clear();
                is_logined_ = false;
                cerr<<"closed"<<endl;
                stop();
            }
            // 发送消息 /s message
            else if(str.size() >= 4 && str[0] == '/' && str[1] =='s') {
                if(!is_logined_) {
                    cerr<<"login first"<<endl;
                } else {
                    out<<user_<<" : "<<str.substr(3)<<endl;
                }
            }
            // 登陆 “/l username”
            else if(str.size() >= 4 && str[0] == '/' && str[1] == 'l') {
                if (!is_logined_) {
                    user_ = str.substr(3);
                    is_logined_ = true;
                } else {
                    cerr<<"err: already logined!"<<endl;
                }
            } else {
                print_err();
            }
        }

    }

    void print_err() {
           cerr<<"err: bad cmd!"<<endl
            <<"usage:"<<endl
            <<"login: /l username"<<endl
            <<"exit: /q"<<endl
            <<"send: /s xxxxxxxxxxxx"<<endl;
    }

private:
    orchid::scheduler sche_;
    orchid::descriptor stdin_;
    orchid::descriptor stdout_;
    orchid::socket sock_;
    string user_;
    bool is_logined_;
    string ip_;
    string port_;

};

int main(int argc,char* argv[]) {
    string ip = argv[1];
    string port = argv[2];
    chat_client client(ip,port);
    client.run();
}

在上面的代码中,我们利用了boost.bind的强大能力,使用类的成员函数创建了协程。

然后是chat server:

chat server中有2个类,server和client。类server实现了chat server的主要逻辑,类client则是客户端代理类,负责从客户端处接收和发送数据。

server类的职责包括:维护客户端列表,广播某个客户端的发来的消息。 因此server处理的消息有2类,第一类是控制消息(下面代码中的ctrl_t类型),代表了客户端的到来和离开事件; 另外一类是文本消息,server类需要向所有的客户端转发、广播该类消息。不管是处理第一种类型的消息还是处理第二种类型的消息,都需要访问到其内部维护的客户端列表。为了同步这些访问,我们需要在同一个chan中接收这两种消息。

const static std::size_t STACK_SIZE = 64*1024;

template <typename Client>
struct server {
    ///typedef/
    typedef server<Client> self_type;
    typedef boost::shared_ptr<Client> client_sp_type; 
    typedef std::list<client_sp_type> client_list_type;// 客户端列表类型,
                                                        // 保存的是智能指针。
    enum {REGISTER,UNREGISTER};
    struct ctrl_t {
        int cmd_;
        client_sp_type client_;
    };
    typedef boost::variant<string,ctrl_t> msg_type;//消息类型

    //变量///
    orchid::scheduler_group schedulers_;
    orchid::acceptor acceptor_;
    std::string port_;
    orchid::chan<msg_type> msg_ch_;//server的消息队列
    client_list_type clients_;//客户端列表
    ///

    server(std::size_t size,const string& port)
        :schedulers_(size),acceptor_(schedulers_[0].get_io_service()),port_(port),msg_ch_(512) {
    }
    ~server() {
    }

    //handle_msg是消息处理协程。不断的从消息队列中读取消息,然后判断消息类型,并处理。
    void handle_msg(orchid::coroutine_handle co) {
        msg_type msg;
        for (;;) {
            msg_ch_.recv(msg,co);
            if(msg.which() == 0) {// 如果是string类型,即文本消息,则向所有的客户端代理广播。
                for(typename client_list_type::iterator it = clients_.begin(); it != clients_.end(); ++it) {
                    //向客户端代理的chan中发送消息。
                    (*it) -> ch_.send(boost::get<string>(msg),co);
                }
            } else if(msg.which() == 1) {//如果是ctrl_t类型,即控制消息,则修改客户端列表。
                if(boost::get<ctrl_t>(msg).cmd_ == REGISTER) {//注册消息
                    clients_.push_back(boost::get<ctrl_t>(msg).client_);
                } else if(boost::get<ctrl_t>(msg).cmd_ == UNREGISTER) {//反注册消息
                    clients_.remove(boost::get<ctrl_t>(msg).client_);
                } else {
                    throw std::runtime_error("unkonw cmd! should never hanppened!");
                }
            } else {
                throw std::runtime_error("unkonw msg! should never hanppened!");
            }
        }
    }

    //处理连接到来事件。当连接到来时,发送表示注册的控制消息到消息队列中。
    void handle_accept(orchid::coroutine_handle co) {
    try {
        int index = 1;
        acceptor_.bind_and_listen(port_);
        for (;;) {
            if(index >= schedulers_.size()) index = 0;
            boost::shared_ptr<Client> c(new Client(schedulers_[index++],*this));
            acceptor_.accept(c->sock_,co);
            c -> start();
            ctrl_t msg;
            msg.cmd_ = REGISTER;
            msg.client_ = c;
            msg_ch_.send(msg,co);
        }
    } catch (boost::system::system_error& e) {
        cout<<e.code()<<" "<<e.what()<<endl;
    }
}

    void run() {
        schedulers_[0].spawn(boost::bind(&self_type::handle_accept,this,_1),STACK_SIZE);
        schedulers_[0].spawn(boost::bind(&self_type::handle_msg,this,_1),STACK_SIZE);
        schedulers_.run();
    }

};

客户端代理类的主要职责是:接收客户端socket上的数据,并发送到server类中;接收server发来的消息,并发送到客户端socket中;当客户端断开连接时,通知server。 不难看出,一个客户端代理类对象会同时被3个协程访问到:server的消息处理协程,client的sender和receiver协程。为了安全的释放客户端代理类的对象,我们使用引用计数的方案。引用计数通过boost::enable_shared_from_this来实现。

//客户端代理类。
struct client:public boost::enable_shared_from_this<client> {
    orchid::scheduler& sche_;
    server<client>& server_;
    orchid::socket sock_;
    orchid::chan<string> ch_;

    client(orchid::scheduler& sche,server<client>& s)
        :sche_(sche),server_(s),
        sock_(sche_.get_io_service()),ch_(32) {

    }
    ~client() {

    }

    // 启动发送和接收协程。注意这个地方的 this -> shared_from_this(),
    // 启动的协程中保存了当前对象的智能指针。
    // 当协程结束后,智能指针会释放,并在引用计数为0时,释放当前对象。
    void start() {
         sche_.spawn(boost::bind(&client::sender,this -> shared_from_this(),_1),STACK_SIZE);
         sche_.spawn(boost::bind(&client::receiver,this -> shared_from_this(),_1),STACK_SIZE);
    }

    //不断从client的消息队列中接收消息,并通过socket发送。
    //chan被关闭后退出循环,协程结束。
    void sender(orchid::coroutine_handle& co) {
        string str;
        orchid::tcp_ostream out(sock_,co);
        while(ch_.recv(str,co)) {
            out<<str<<endl;
        }
    }

    // 不断从客户端接收消息,直到客户端断开连接。
    // 当连接断开后,关闭自身的chan并发送反注册信息到server的消息队列中。
    void receiver(orchid::coroutine_handle& co) {
        orchid::tcp_istream in(sock_,co);
        //客户端断开连接后会退出循环。
        for (string str;std::getline(in,str);) {
            //向server的chan中发送消息。
            server_.msg_ch_.send(str,co);
        }
        ch_.close();
        server<client>::ctrl_t ctrl_msg;
        ctrl_msg.cmd_ = server<client>::UNREGISTER;
        ctrl_msg.client_ = this -> shared_from_this();
        server_.msg_ch_.send(ctrl_msg, co);

    }
};

int main(int argc,char* argv[]) {
    string port = argv[1];
    server<client> s(4,port);
    s.run();
}

需要注意的是,对同一个套接字进行读写的协程应该建立在同一个调度器上。因为实际上从green化的IO对象的构造函数即可以看出:green化的IO对象是与调度器中io_service对象绑定的。当不同调度器上的协程访问同一个IO对象的时,orchid不能保证其行为的正确性。

转载于:https://www.cnblogs.com/fuland/p/3736735.html

 类似资料: