协程 Server

Swoole在2.0开始内置协程(Coroutine)的能力,提供了具备协程能力IO接口(统一在命名空间Swoole\Coroutine\*)。

2.0.2或更高版本已支持PHP7
2.0.8或更高版本已默认开启--enable-coroutine,可使用--disable-coroutine关闭协程特性

协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低。Swoole可以为每一个请求创建对应的协程,根据IO的状态来合理的调度协程,这会带来了以下优势:

  1. 开发者可以无感知的用同步的代码编写方式达到异步IO的效果和性能,避免了传统异步回调所带来的离散的代码逻辑和陷入多层回调中导致代码无法维护。

  2. 同时由于swoole是在底层封装了协程,所以对比传统的php层协程框架,开发者不需要使用yield关键词来标识一个协程IO操作,所以不再需要对yield的语义进行深入理解以及对每一级的调用都修改为yield,这极大的提高了开发效率。

协程API目前针对了TCP,UDP等主流协议client的封装,包括:

  • UDP
  • TCP
  • HTTP
  • Mysql
  • Redis

可以满足大部分开发者的需求。对于私有协议,开发者可以使用协程的TCP或者UDP接口去方便的封装。

启用

Prerequisite:

  • PHP版本要求:>= 7.0,包括7.0、7.1、7.2
  • 基于swoole_server或者swoole_http_server进行开发,目前只支持在onRequet, onReceive, onConnect等事件回调函数中使用协程。

swoole_serverswoole_http_server将为每一个请求创建对应的协程,开发者可以在onRequetonReceiveonConnect 事件回调中使用协程客户端。

相关配置

Swoole\Serverset方法中增加了一个配置参数max_coro_num,用于配置一个Worker进程最多同时处理的协程数目。因为随着Worker进程处理的协程数目的增加,其占用的内存也会增加,为了避免超出php的memory_limit限制,请根据实际业务的压测结果设置该值,默认为3000

使用示例

$http = new swoole_http_server("127.0.0.1", 9501);

$http->on("request", function ($request, $response) {
    $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
    $client->connect("127.0.0.1", 8888, 0.5);
    //调用connect将触发协程切换
    $client->send("hello world from swoole");
    //调用recv将触发协程切换
    $ret = $client->recv();
   $response->header("Content-Type", "text/plain");
    $response->end($ret);
    $client->close();
});

$http->start();

当代码执行到connect()和recv()函数时,swoole会触发进行协程切换,此时swoole可以去处理其他的事件或者接受新的请求。当此client连接成功或者后端服务回包后,swoole server会恢复协程上下文,代码逻辑继续从切换点开始恢复执行。开发者整个过程不需要关心整个切换过程。具体使用可以参考client的文档。

注意事项

  1. 全局变量:协程使得原有的异步逻辑同步化,但是在协程的切换是隐式发生的,所以在协程切换的前后不能保证全局变量以及static变量的一致性。
  2. 请勿在2.2以下的版本的以下场景中触发协程切换:
    • 析构函数
    • 魔术方法__call() __get() __set()
  3. gcc 4.4下如果在编译swoole的时候(即make阶段),出现gcc warning: dereferencing pointer ‘v.327’ does break strict-aliasing rulesdereferencing type-punned pointer will break strict-aliasing rules 请手动编辑Makefile,将CFLAGS = -Wall -pthread -g -O2替换为CFLAGS = -Wall -pthread -g -O2 -fno-strict-aliasing,然后重新编译make clean;make;make install
  4. 与xdebug、xhprof、blackfire等zend扩展不兼容,例如不能使用xhprof对协程server进行性能分析采样。
  5. 原生的call_user_func和call_user_func_array中无法使用协程client,请使用\Swoole\Coroutine::call_user_func和\Swoole\Coroutine::call_user_func_array代替,在PHP7中如果无法保证在编译时反射调用的类是编译器已知的,请统一使用协程版反射调用

  • threestone

    你在逗我么?越玩越大?

  • 小心跳大

    腻害腻害

  • 王金贝

    nice~~~ 牛牛牛

  • php7

    啥时候支持php7呢

  • 大鹏

    666

  • 张素杰

    [good]

  • tmacfan

    协程的切换是怎么做到隐式的,能不能说明一下原理

  • xhc

    什么版本可以支持手动触发协程切换?

  • fingertips of happines

    话说,坑就坑吧,没必要说坑爹吧!

  • 马秉尧

    PHP 7 中,使用原生的 call_user_func 和 call_user_func_array 调用包含有协程代码的函数也会让程序挂起不能执行。

  • chunk

    configure: WARNING: unrecognized options: --enable-coroutine

  • 高秋亭-北京展程科技有限

    hello 请问一下你这个问题解决了吗?

  • Peterkwok

    unrecognized options: --enable-coroutine

  • mak

    好像只能在server中事件的回调函数中直接使用,如果写在外面的函数或者类里面,会出现问题,函数的return和协程不能正常地切回函数调用的上下文。

  • 刘小跃

    可以变通一下: public function onReceive:(...........){ $swoole_mysql = new Swoole\Coroutine\MySQL(); $swoole_mysql->connect($config); $res = Foo::bar($swoole_mysql, [..............]); } 然后在Foo::bar方法里面随便用。

  • 迷宫罐

    协程这一系列的工具,必须在server的onRequest等环境下才能使用,不方便调试。 是否有什么方法可以判断,当前是否处于可以使用协程的环境下呢?

  • 戦场原礼亜_err500

    我也想知道协程的切换是怎么做到隐式的。 按说任务调度器直接运行协程程序才能使用yield语法进行协程切换呀

  • 宇宙星辰

    什么时候能支持capwap协议

  • 江南夜雨

    根据我的理解,估计worker进程在遇到io调用(网络io或文件io)就会切换协程,所以做到隐式切换的,然后加入到eventloop,当事件发生时再唤醒协程,猜测,不对勿喷^^

  • Gavin_new

    有golang协程的影子

  • dana

    我把 这个示列代码 拿过去连接失败 9501 可以连接 后面的不可以 这个怎么解决

  • dana

    为什么实例 的clinet 连接失败