swoole 协程

先阅读一下官方的协程文档章节

  • 一、什么是协程

  • 二、协程带来的优势(为什么需要协程、为什么需要协程)

  • 三、协程实现(redis示例,mysql示例)

  • 四、协程的性能测试

  • 五、协程的注意问题

一、什么是协程

协程(Coroutine)也叫用户级线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低

Swoole可以为每一个请求创建对应的协程,根据IO的状态来合理的调度协程,线程和进程的调度是由操作系统来调控, 而协程的调度由用户自己调控。

协程调度器可以在协程A即将进入阻塞IO操作, 将该协程挂起,把当前的栈信息 StackA 保存下来,然后切换到协程B,等到协程A的该 IO操作返回时, 再根据 Stack A 切回到之前的协程A当时的状态。协程相对于事件驱动是一种更先进的高并发解决方案,把复杂的逻辑和异步都封装在底层,让程序员在编程时感觉不到异步的存在。

二、协程带来的优势

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

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

注意

  • 1、Swoole的协程在底层实现上是单线程的,因此同一时间只有一个协程在工作,协程的执行是串行的。这与线程不同,多个线程会被操作系统调度到多个CPU并行执行。

  • 2、协程遇到io才会切换,单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行

三、协程实现

1、swoole的两种命名空间形式

Swoole支持两种形式的命名空间一种是Swoole\Coroutine,2.2.0以上可使用Co\命名空间短命名简化类名。

2、协程默认支持的位置

Swoole2仅有部分事件回调函数底层自动创建了协程,以下回调函数可以调用协程客户端,可以查看这里https://wiki.swoole.com/wiki/page/696.html

在不支持协程的位置可以使用goCo::create创建协程

3、协程并发

协程其实也是阻塞运行的,如果,在一个执行中,比如同时查redis,再去查mysql,即使用了上面的协程,也是顺序执行的。那么可不可以几个协程并发执行呢?

通过延迟收包的形式获取,遇到到IO 阻塞的时候,协程就挂起了,不会阻塞在那里等着网络回报,而是继续往下走,swoole当中可以用setDefer()方法声明延迟收包然后通过recv()方法收包。

可以具体参考一下官方文档并发调用章节 ,用setDefer()方法声明延迟收包然后通过recv()方法收包。

示例

<?php

$server=new swoole\http\server("0.0.0.0",9503);

//swoole会开辟一个协程栈,对协程栈进行初始化
$server->on('request',function(){

    $time=time();
    $swoole_mysql = new Co\MySQL();
    $swoole_mysql->connect([
        'host' => '127.0.0.1',
        'port' => 3306,
        'user' => 'root',
        'password' => '',
        'database' => 'test',
    ]);

    //max(mysql(3),mysql(1));
    $swoole_mysql->setDefer(); //延迟收包
    $swoole_mysql->query('select sleep(3)'); //阻塞
    //$swoole_mysql->close();

    $swoole_mysql1 = new Co\MySQL();
    $swoole_mysql1->connect([
        'host' => '127.0.0.1',
        'port' => 3306,
        'user' => 'root',
        'password' => '',
        'database' => 'test',
    ]);
    $swoole_mysql1->setDefer();
    $res = $swoole_mysql1->query('select sleep(4)');
    //$swoole_mysql1->close();

    var_dump($swoole_mysql1->recv(),$swoole_mysql->recv());

    //同步代码
    echo  time()-$time.PHP_EOL;

});

$server->start();

4、 协程通讯

使用本地内存,不同的进程之间内存是隔离的。只能在同一进程的不同协程内进行push和pop操作

向通道中写入数据。

function Coroutine\Channel->push(mixed $data) : bool;

从通道中读取数据。

function Coroutine\Channel->pop() : mixed;

对协程调用场景,最常见的“生产者-消费者”事件驱动模型,一个协程负责生产产品并将它们加入队列,另一个负责从队列中取出产品并使用它。

redis协程示例

<?php
$http = new swoole_http_server('0.0.0.0', 8001);

$http->on('request', function($request, $response) {
    // 获取redis 里面 的key的内容, 然后输出浏览器

    $redis = new Swoole\Coroutine\Redis();
    $redis->connect('127.0.0.1', 6379);
    $value = $redis->get($request->get['a']);

    $response->header("Content-Type", "text/plain");
    $response->end($value);
});

$http->start();

mysql协程示例

<?php
$server=new swoole\http\server("0.0.0.0",9503);

//swoole会开辟一个协程栈,对协程栈进行初始化
$server->on('request',function(){

    $swoole_mysql = new Co\MySQL();
    $swoole_mysql->connect([
        'host' => '127.0.0.1',
        'port' => 3306,
        'user' => 'root',
        'password' => 'Qq!990979940',
        'database' => 'test',
    ]);

    //收包
    $res = $swoole_mysql->query('select sleep(1)');
    $swoole_mysql->close();
    var_dump($res);
});

$server->start();

四、协程的性能测试

1、 安装ab测试工具

yum install apr-util
yum -y install httpd-tools

2、 ab介绍

参考这篇文章 : 超实用压力测试工具-ab工具

五、协程的注意问题

如果在多个协程间共用同一个协程客户端,同步阻塞程序不同,协程是并发处理请求的,因此同一时间可能会有很多个请求在并行处理,一旦共用客户端连接,就会导致不同协程之间发生数据错乱。

Last updated