什么是协程

协程(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. 二:使用VS2019 + .net 6创建 webapi 项目

    0.创建一个.net 6项目.由于目前.net 6还是预览版,所以需要添加预览版SDK功能.工具 -> 选项 -> 环境 -> 预览功能 ,勾选使用.net sdk预览版. 1.新建 ...

  2. springboot对mybatis的整合

    1. 导入依赖 首先新建一个springboot项目,勾选组件时勾选Spring Web.JDBC API.MySQL Driver pom.xml配置文件导入依赖 <!--mybatis-sp ...

  3. 支持边云协同终身学习特性,KubeEdge子项目Sedna 0.3.0版本发布!

    摘要:随着边缘设备数量指数级增长以及设备性能的提升,边云协同机器学习应运而生,以期打通机器学习的最后一公里. 本文分享自华为云社区<支持边云协同终身学习特性,KubeEdge子项目Sedna 0 ...

  4. GO学习-(28) Go语言操作influxDB

    Go语言操作influxDB 本文介绍了influxDB时序数据库及Go语言操作influxDB. InfluxDB是一个开源分布式时序.事件和指标数据库.使用Go语言编写,无需外部依赖.其设计目标是 ...

  5. CAP 5.1 版本发布通告 - 你期待的 Redis 来了

    前言 今天,我们很高兴宣布 CAP 发布 5.1 版本正式版,在这个版本里我们同样引入了更多令人激动的新特性和改进,同时也得到越来越多人的喜爱. 得益于社区的反馈和贡献者的支持,在过去的两个月里,我们 ...

  6. JAVA并发(7)-并发队列PriorityBlockingQueue的源码分析

    本文讲PriorityBlockingQueue(优先阻塞队列) 1. 介绍 一个无界的具有优先级的阻塞队列,使用跟PriorityQueue相同的顺序规则,默认顺序是自然顺序(从小到大).若传入的对 ...

  7. JVM快速入门(上)

    前言 根据狂神说的JVM快速入门做了以下笔记,讲的很好的一个博主,给小伙伴们附上视频链接狂神说JVM快速入门    接下来我按照他所讲的内容给大家记录一些重点! 一.JVM体系结构 .java经由ja ...

  8. 十一、设置Nginx开机自启动

    一.创建nginx.service文件 [root@svr7 ~]# vim /lib/systemd/system/nginx.service [Unit]Description=nginx ser ...

  9. 再看 Java 中的单例

    此前面试遇到了单例问题,本以为已经背的滚瓜烂熟,没想到被问单例如何避免被反射和序列化破坏,虽然后来还是等到了通知,但还是复习一下单例的实现方式,并学习防止反射和序列化破坏的手段. 基本实现方式 其他相 ...

  10. Linux命令基础(二)

    一.列表显示目录内容-ls 1.显示目录中内容,包括子目录和文件相关属性信息 ls(列表的形式去显示目录内容)                    [选项](可有可无的)              ...