什么是协程

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

要理解是什么是“用户态的线程”,必然就要先理解什么是“内核态的线程”。内核态的线程是由操作系统来进行调度的,在切换线程上下文时,要先保存上一个线程的上下文,然后执行下一个线程,当条件满足时,切换回上一个线程,并恢复上下文。协程也是如此,只不过,用户态的线程不是由操作系统来调度的,而是由程序员来调度的,就是所谓的用户态的线程。

协程的执行流程  

协程的适用场景

高并发服务,如秒杀系统、高性能API接口、RPC服务器,使用协程模式,服务的容错率会大大增加,某些接口出现故障时,不会导致整个服务崩溃。

爬虫,可实现非常巨大的并发能力,即使是非常慢速的网络环境,也可以高效地利用带宽。

即时通信服务,如IM聊天、游戏服务器、物联网、消息服务器等等,可以确保消息通信完全无阻塞,每个消息包均可即时地被处理。

协程与线程区别

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

一个协程正在运行时,其他协程会停止工作。当前协程执行阻塞IO操作时会挂起,底层调度器会进入事件循环。当有IO完成事件时,底层调度器恢复事件对应的协程的执行。

对CPU多核的利用,仍然依赖于Swoole引擎的多进程机制。

协程实现

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

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

2、协程默认支持的位置

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

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

3、协程的性能测试

通过多个协程连接redis操作对比没有使用协程的方式

4、协程并发

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

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

5、协程通讯

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

向通道中写入数据。

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

从通道中读取数据。

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

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

6、协程的注意问题

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

swoole通用协程池的实现

swoole官方的协程池是用只能用在Redis。因为协程池代码层耦合了Redis实例化逻辑。通过工厂函数实现了通用性。

class RedisPool
{
    /**
     * @var \Swoole\Coroutine\Channel
     */
    protected $pool;

    /**
     * RedisPool constructor.
     * @param int $size 连接池的尺寸
     */
    function __construct($size = 100)
    {
        $this->pool = new Swoole\Coroutine\Channel($size);
        for ($i = 0; $i < $size; $i++)
        {
            $redis = new Swoole\Coroutine\Redis();
            $res = $redis->connect('127.0.0.1', 6379);
            if ($res == false)
            {
                throw new RuntimeException("failed to connect redis server.");
            }
            else
            {
                $this->put($redis);
            }
        }
    }

    function put($redis)
    {
        $this->pool->push($redis);
    }

    function get()
    {
        return $this->pool->pop();
    }
}

利用工厂方法的改造如下:

<?php
/**
 * @author xialeistudio
 * @date 2019-05-20
 */

namespace swoole\foundation\pool;

use Swoole\Coroutine\Channel;

/**
 * Swoole generic connection pool
 * Class Pool
 * @package swoole\foundation\pool
 */
class GenericPool
{
    /**
     * @var int pool size
     */
    private $size = 0;
    /**
     * @var callable construct a connection
     */
    private $factory = null;
    /**
     * @var Channel
     */
    private $channel = null;

    /**
     * GenericPool constructor.
     * @param int $size
     * @param callable $factory
     * @throws InvalidParamException
     */
    public function __construct($size, callable $factory)
    {
        $this->size = $size;
        $this->factory = $factory;
        $this->init();
    }

    /**
     * check pool config
     * @throws InvalidParamException
     */
    private function init()
    {
        if ($this->size <= 0) {
            throw new InvalidParamException('The "size" property must be greater than zero.');
        }
        if (empty($this->factory)) {
            throw new InvalidParamException('The "factory" property must be set.');
        }
        if (!is_callable($this->factory)) {
            throw new InvalidParamException('The "factory" property must be callable.');
        }
        $this->bootstrap();
    }

    /**
     * bootstrap pool
     */
    private function bootstrap()
    {
        $this->channel = new Channel($this->size);

        for ($i = 0; $i < $this->size; $i++) {
            $this->channel->push(call_user_func($this->factory));
        }
    }

    /**
     * Acquire a connection
     * @param int $timeout
     * @return mixed
     */
    public function acquire($timeout = 0)
    {
        return $this->channel->pop($timeout);
    }

    /**
     * Release a resource
     * @param mixed $resource
     */
    public function release($resource)
    {
        $this->channel->push($resource);
    }
}

协程与Swoole的原理,相关应用以及适用场景等的更多相关文章

  1. 聊一聊Unity协程背后的实现原理

    Unity开发不可避免的要用到协程(Coroutine),协程同步代码做异步任务的特性使程序员摆脱了曾经异步操作加回调的编码方式,使代码逻辑更加连贯易读.然而在惊讶于协程的好用与神奇的同时,因为不清楚 ...

  2. Unity协程(Coroutine)原理深入剖析

    Unity协程(Coroutine)原理深入剖析 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 其实协程并没有那么复杂,网上很多地方都说是多 ...

  3. Unity协程(Coroutine)原理深入剖析(转载)

    记得去年6月份刚开始实习的时候,当时要我写网络层的结构,用到了协程,当时有点懵,完全不知道Unity协程的执行机制是怎么样的,只是知道函数的返回值是IEnumerator类型,函数中使用yield r ...

  4. Unity协程(Coroutine)原理深入剖析再续

    Unity协程(Coroutine)原理深入剖析再续 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面已经介绍过对协程(Coroutine ...

  5. 【转】Unity协程(Coroutine)原理深入剖析

    Unity协程(Coroutine)原理深入剖析 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 记得去年6月份刚开始实习的时候,当时要我写网 ...

  6. Python协程的引入与原理分析

    相关概念 并发:指一个时间段内,有几个程序在同一个cpu上运行,但是任意时刻只有一个程序在cpu上运行.比如说在一秒内cpu切换了100个进程,就可以认为cpu的并发是100. 并行:值任意时刻点上, ...

  7. 深入浅出!从语义角度分析隐藏在Unity协程背后的原理

    Unity的协程使用起来比较方便,但是由于其封装和隐藏了太多细节,使其看起来比较神秘.比如协程是否是真正的异步执行?协程与线程到底是什么关系?本文将从语义角度来分析隐藏在协程背后的原理,并使用C++来 ...

  8. Swoole协程与传统fpm同步模式比较

    如果说数组是 PHP 的精髓,数组玩得不6的,根本不能算是会用PHP.那协程对于 Swoole 也是同理,不理解协程去用 Swoole,那就是在瞎用. 首先,Swoole 只能运行在命令行(Cli)模 ...

  9. Python 中的进程、线程、协程、同步、异步、回调

    进程和线程究竟是什么东西?传统网络服务模型是如何工作的?协程和线程的关系和区别有哪些?IO过程在什么时间发生? 一.上下文切换技术 简述 在进一步之前,让我们先回顾一下各种上下文切换技术. 不过首先说 ...

随机推荐

  1. C/C++ 复习

    本文总结一下C++面试时常遇到的问题.C++面试中,主要涉及的考点有 关键字极其用法,常考的关键字有const, sizeof, typedef, inline, static, extern, ne ...

  2. 10.1 ifconfig:配置或显示网络接口信息

    ifconfig命令 用于配置网卡IP地址等网络参数或显示当前网络的接口状态,其类似于Windows下的ipconfig命令,这两个命令很容易混淆,读者需要区分一下.此外,ifconfig命令在配置网 ...

  3. IntelliJ IDEA配置tomcat 教程

    1.点击Run-Edit Configurations... 2.点击左侧"+",选择Tomcat Server--Local 3.在Tomcat Server -> Unn ...

  4. SystemVerilog数组(一)

  5. python基础课程讲解

    day01: 编程语言的介绍: 一 1.什么是编程?(****) 两个环节: 1.把做事的思维逻辑给想清楚了 2.用计算机能听懂的语言也就是编程语言把做事的步骤给翻译下来 2.为什么要编程? 人要奴役 ...

  6. IDEA debug ConcurrentLinkedQueue时抽风

    1. 介绍 如标题所见,我在使用IDEA debug ConcurrentLinkedQueue的Offer方法时,发生了下面的情况. 代码如下: ConcurrentLinkedQueue<s ...

  7. supervisor 使 celery后台运行

    1.安装 supervisor pip install supervisor  2.创建supervisor配置文件,命令如下: 进入项目文件 echo_supervisord_conf > s ...

  8. 纯C++代码实现将像素矩阵保存为bmp图片

    由于工作需要,时常需要将像素矩阵保存图片显示观看.为此,特地总结了三种使用纯C++代码生成bmp图片的方法.分别是使用自定义数据.从外界导入的txt和csv以及从图片中导入的数据. 1.使用自定义数据 ...

  9. 回文词——线性dp

    #include<iostream> #include<cstdio> using namespace std; int n,f[5002][5002]; char str1[ ...

  10. 自定义Kubernetes调度程序来编排高可用性应用程序

    自定义Kubernetes调度程序来编排高可用性应用程序 只要愿意遵守规则,在Kubernetes上进行部署和乘飞机旅行就可以很愉快.通常,事情会"正常工作".但是,如果有兴趣与必 ...