专栏首页日常学习Swoole—csp编程模型

Swoole—csp编程模型

协程

不需要操作系统参与,创建销毁和切换的成本非常低,遇到io会自动让出cpu执行权,交给其它协程去执行。

Swoole协程

非协程代码:

<?php
$start = time();
for ($i = 0; $i < 500; $i++) {
    file_get_contents('http://www.easyswoole.com/');
    echo '任务' . $i . '完成' . PHP_EOL;
}
echo '非协程总耗时' . (time() - $start) . 's' . PHP_EOL;

执行结果:

任务495完成
任务496完成
任务497完成
任务498完成
任务499完成
非协程总耗时13s

协程代码:

<?php
$start = time();
for ($i = 0; $i < 500; $i++) {
    Swoole\Coroutine::create(function () use ($i, $start) {
        $client = new Swoole\Coroutine\Http\Client('www.easyswoole.com', 80);
        $client->get('/');
        echo '任务' . $i . '完成' . '耗时' . (time() - $start) . 's' . PHP_EOL;
    });
}

执行结果:

任务389完成耗时1s
任务395完成耗时1s
任务434完成耗时1s
任务477完成耗时1s
任务469完成耗时1s
任务385完成耗时1s
任务498完成耗时1s

可以发现速度相当快,但任务的id,不是顺序执行的,这就是遇到了ioswoole底层自动切换让出cpu执行权。

Channle

用于协程间通讯,支持多生产者协程和多消费者协程。底层自动实现了协程的切换和调度。

执行下面代码:

$start = time();
function task1()
{
    Swoole\Coroutine::sleep(3); // 模拟io阻塞
    echo "发短信\n";
}

function task2()
{
    Swoole\Coroutine::sleep(3); // 模拟io阻塞
    echo "发邮件\n";
}

Swoole\Coroutine::create('task1');
Swoole\Coroutine::create('task2');
echo '总耗时' . (time() - $start) . 's' . PHP_EOL;

执行结果:

总耗时0s
发短信
发邮件

却发现以上代码,先执行的echo '总耗时' . (time() - $start) . 's' . PHP_EOL;

要等待task1task2执行成功后输出,该怎么半呢,这就利用了channel,来实现csp并发编程。

代码:

<?php
Swoole\Coroutine::create(function (){
    $start = time();
    $channel = new Swoole\Coroutine\Channel();
    function task1($channel)
    {
        /** @var Swoole\Coroutine\Channel $channel */
        Swoole\Coroutine::sleep(3); // 模拟io阻塞
        $channel->push("发短信\n");
    }

    function task2($channel)
    {
        /** @var Swoole\Coroutine\Channel $channel */
        Swoole\Coroutine::sleep(3); // 模拟io阻塞
        $channel->push("发邮件\n");
    }

    Swoole\Coroutine::create('task1', $channel);
    Swoole\Coroutine::create('task2', $channel);

    for ($i = 0; $i < 2; $i++) {
        echo $channel->pop();
    }

    echo '总耗时' . (time() - $start) . 's' . PHP_EOL;
});

执行结果:

发短信
发邮件
总耗时3s

可以看到耗时3s,但我们在增加一个任务,for里面的$i就要修改,使得我们的代码非常繁琐,所以就有了WaitGroup

channel可以实现协程通信,依赖管理,协程同步。

实现连接池功能可以看我之前的文章,传送门

WaitGroup

基于Channel实现的Golangsync.WaitGrup功能。

方法:

  • add 方法增加计数
  • done 表示任务已完成
  • wait 等待所有任务完成恢复当前协程的执行
  • WaitGroup 对象可以复用,adddonewait 之后可以再次使用

代码:

<?php
Swoole\Coroutine::create(function () {

    $start = time();
    $waitGroup = new Swoole\Coroutine\WaitGroup();
    function task1($waitGroup)
    {
        /** @var WaitGroup $waitGroup */
        Swoole\Coroutine::sleep(3); // 模拟io阻塞
        echo "发短信\n";
        $waitGroup->done();;
    }

    function task2($waitGroup)
    {
        /** @var WaitGroup $waitGroup */
        Swoole\Coroutine::sleep(3); // 模拟io阻塞
        echo "发邮件\n";
        $waitGroup->done();

    }

    $waitGroup->add();
    Swoole\Coroutine::create('task1', $waitGroup);

    $waitGroup->add();
    Swoole\Coroutine::create('task2', $waitGroup);

    $waitGroup->wait();

    echo '总耗时' . (time() - $start) . 's' . PHP_EOL;
});

执行结果跟之前一样,也是好时3s,但是不是更简单了呢。

Context

协程原有的异步逻辑同步化,但是在协程切换是隐式发生的,所有协程切换的前后不能保证全局遍历及static变量的一致性。

context用协程id做隔离,来保存上下文内容。

代码复现:

<?php
class Email
{
    static $email = null;
}

Swoole\Coroutine::create(function () {

    function task1($email)
    {
        Email::$email = $email;
        Swoole\Coroutine::sleep(3); // 模拟io阻塞
        echo "发邮件:" . Email::$email . PHP_EOL;
    }

    function task2($email)
    {
        Email::$email = $email;
        echo "发邮件:" . Email::$email . PHP_EOL;

    }

    Swoole\Coroutine::create('task1', '975975398@gmail.com');

    Swoole\Coroutine::create('task2', 'gaobinzhan@gmail.com');

});

从感觉啥觉得会输出两个邮箱地址,但其实:

发邮件:gaobinzhan@gmail.com
发邮件:gaobinzhan@gmail.com

这就是变量生命周期,需要注意,我们可以封装一个类来保存上下文。

<?php
class Context
{
    /**
     * [
     *    'cid' => [  // 就是协程的id
     *        'key' => 'value' // 保存的全局变量的信息
     *    ]
     * ]
     * @var [type]
     */
    public static $pool = [];

    static function get($key)
    {
        $cid = Swoole\Coroutine::getuid();// 获取当前运行的协程id
        if ($cid < 0) {
            return null;
        }
        if (isset(self::$pool[$cid][$key])) {
            return self::$pool[$cid][$key];
        }
        return null;
    }

    static function put($key, $item)
    {
        $cid = Swoole\Coroutine::getuid();// 获取当前运行的协程id
        if ($cid > 0) {
            self::$pool[$cid][$key] = $item;
        }
    }

    static function delete($key = null)
    {
        $cid = Swoole\Coroutine::getuid();
        if ($cid > 0) {
            if ($key) {
                unset(self::$pool[$cid][$key]);
            } else {
                unset(self::$pool[$cid]);
            }
        }
        var_dump(self::$pool);
    }
}

运行以下代码:

<?php
Swoole\Coroutine::create(function () {

    function task1($email)
    {
        Context::put('email',$email);
        Swoole\Coroutine::sleep(3); // 模拟io阻塞
        echo "发邮件:" . Context::get('email') . PHP_EOL;
    }

    function task2($email)
    {
        Context::put('email',$email);
        echo "发邮件:" . Context::get('email') . PHP_EOL;
    }

    Swoole\Coroutine::create('task1', '975975398@gmail.com');

    Swoole\Coroutine::create('task2', 'gaobinzhan@gmail.com');

    var_dump(Context::$pool);
});

运行结果:

发邮件:gaobinzhan@gmail.com
array(2) {
  [2]=>
  array(1) {
    ["email"]=>
    string(19) "975975398@gmail.com"
  }
  [3]=>
  array(1) {
    ["email"]=>
    string(20) "gaobinzhan@gmail.com"
  }
}
发邮件:975975398@gmail.com

可以看到,两个邮箱都输出成功了,但是我们的变量没有销毁,如何销毁呢,Swoole提供了defer方法,在协程关闭之前会调用defer

<?php
Swoole\Coroutine::create(function () {

    function task1($email)
    {
        Context::put('email',$email);
        Swoole\Coroutine::sleep(3); // 模拟io阻塞
        echo "发邮件:" . Context::get('email') . PHP_EOL;
        Swoole\Coroutine::defer(function (){
            Context::delete();
        });
    }

    function task2($email)
    {
        Context::put('email',$email);
        echo "发邮件:" . Context::get('email') . PHP_EOL;
        Swoole\Coroutine::defer(function (){
            Context::delete();
        });
    }

    Swoole\Coroutine::create('task1', '975975398@gmail.com');

    Swoole\Coroutine::create('task2', 'gaobinzhan@gmail.com');


});

运行结果:

发邮件:gaobinzhan@gmail.com
array(1) {
  [2]=>
  array(1) {
    ["email"]=>
    string(19) "975975398@gmail.com"
  }
}
发邮件:975975398@gmail.com
array(0) {
}

可以发现到最后为空,已经被清空掉了。

是不是觉得这样写很麻烦,以及不确定在什么时候销毁,然而Swoole提供的Context可以让协程退出后上下文自动清理 (如无其它协程或全局变量引用)。

代码实现:

<?php
Swoole\Coroutine::create(function () {

    function task1($email)
    {
        $context = Swoole\Coroutine::getContext();
        $context->email = $email;
        Swoole\Coroutine::sleep(3); // 模拟io阻塞
        echo "发邮件:" . $context->email . PHP_EOL;
    }

    function task2($email)
    {
        $context = Swoole\Coroutine::getContext();
        $context->email = $email;
        echo "发邮件:" . $context->email . PHP_EOL;
    }

    Swoole\Coroutine::create('task1', '975975398@gmail.com');

    Swoole\Coroutine::create('task2', 'gaobinzhan@gmail.com');
    
});

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • sl-im 基于 Swoft 微服务协程框架和 Layim 网页聊天系统 开发出来的聊天室

    sl-im 是基于 Swoft 微服务协程框架和 Layim 网页聊天系统 所开发出来的聊天室。

    gaobinzhan
  • Go编写好的错误处理

    假设现在有个需求,返回的值是太小了还是太大了,返回不同的错误,最简单的方法直接改造GetFibonacci:

    gaobinzhan
  • php用select实现I/O复用

    在Linux Socket服务器短编程时,为了处理大量客户的连接请求,需要使用非阻塞I/O和复用,select、poll和epoll是Linux API提供的I...

    gaobinzhan
  • Android震动器Vibrator调用

    <uses-permission android:name="android.permission.VIBRATE"/>

    IT大飞说
  • 信息熵的4个量化指标的R代码实现

    国际惯例把0.2以下视为收入绝对平均,0.2-0.3视为收入比较平均;0.3-0.4视为收入相对合理;0.4-0.5视为收入差距较大,当基尼系数达到0.5以上时...

    生信技能树
  • ASP.NET MVC Controller激活系统详解:IoC的应用[上篇]

    所谓控制反转(IoC: Inversion Of Control)简单地说就是应用本身不负责依赖对象的创建和维护,而交给一个外部容器来负责。这样控制权就由应用转...

    蒋金楠
  • 匿名对象和object的转换

    参考http://www.2cto.com/kf/201207/139227.html

    跟着阿笨一起玩NET
  • python 播放声音

    py3study
  • MPSCArrayQueue源码分析

    这里简要根据https://gitee.com/eric_ds/baseutil中的MPSCArrayQueue版本源码进行分析来理解这个高性能的的无锁队列的实...

    开发架构二三事
  • Python 播放声音 音频与beep

    py3study

扫码关注云+社区

领取腾讯云代金券