协程(Coroutine)也叫用户态线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低。协程是进程的补充,或者是互补关系。
要理解是什么是“用户态的线程”,必然就要先理解什么是“内核态的线程”。 内核态的线程是由操作系统来进行调度的,在切换线程上下文时,要先保存上一个线程的上下文,然后执行下一个线程,当条件满足时,切换回上一个线程,并恢复上下文。 协程也是如此,只不过,用户态的线程不是由操作系统来调度的,而是由程序员来调度的,就是所谓的用户态的线程。
通俗的说,协程就是一段段协作方式执行的程序,协作来完成一件事,协作就是协同作业。我们知道团队协同来做事,比个人单独来做事,效率肯定要高,因为团队协同可以发挥各成员的能动性、优势互补。这是拿人来比喻。我们拿做事来比喻举个例子:比如我们做饭,比如有以下环节洗菜、切菜、烧水、炒菜、煮米饭,人作为主体来操作,那么如果按部就班的做,先烧水,再洗菜,在切菜,再炒菜,再煮饭,那这顿饭要做很长时间比如总共30分钟吧,如果我们通过协同方式,先烧水,放灶火上就可以做其他洗菜、切菜的准备,再煮米饭,然后再来洗菜、切菜,再查看煮米饭,再炒菜,...,如此循环往复切换,最后水烧好,米饭也煮好了,菜也炒好了,饭也OK了,这样我们耗时可能只有10-15分钟,看到了吗,这就是生活中的“协程”,由人来合理调度安排不同的环节,充分利用各种不同的资源和时间,来达到提高效率。协程是计算机程序,调用的则是不同的程序,处理者主要由CPU完成,处理对象是各种IO资源,处理的方式是不同的语言编写的程序。我们知道,CPU可以调度不同的程序,让程序调用不同的IO资源,最初的进程是通过CPU频繁的切换来完成调用程序的,是操作系统按一定算法分配的时间片抢占被动方式来切换的,未考虑程序实际执行状况,这样切换程序会带来一定问题,而协程作为一种新的工作模式,可以让程序协作方式来执行,在需要使用CPU时,交给程序处理,遇到耗时的IO资源操作时会让出CPU,交给处理其他程序,这样互相协作来执行,而不是抢占式的,就像交通规则,大家都遵守按一定规则礼让先行,不随便抢道,协同方式,程序都会执行的良好。
Swoole框架主要由韩天峰为主的团队开发的,现在最新版本是4.3.x,框架中大量使用到协程,宣称是“全新PHP编程模式,全新的协程编程模式”,“Swoole的内置协程未来可能会颠覆现代软件的开发模式”,可见对Swoole的协程评价之高。Swoole可以为每一个请求创建对应的协程,根据IO的状态来合理的调度协程,线程和进程的调度是由操作系统来调控, 而协程的调度由用户自己调控。
协程调度器可以在协程A即将进入阻塞IO操作, 将该协程挂起,把当前的栈信息 StackA 保存下来,然后切换到协程B,等到协程A的该 IO操作返回时, 再根据 Stack A 切回到之前的协程A当时的状态。协程相对于事件驱动是一种更先进的高并发解决方案,把复杂的逻辑和异步都封装在底层,让程序员在编程时感觉不到异步的存在。
1、开发者可以无感知的用同步的代码编写方式达到异步IO的效果和性能,避免了传统异步回调所带来的离散的代码逻辑和陷入多层回调中导致代码无法维护。
2、同时由于swoole是在底层封装了协程,所以对比传统的php层协程框架,开发者不需要使用yield关键词来标识一个协程IO操作,所以不再需要对yield的语义进行深入理解以及对每一级的调用都修改为yield,这极大的提高了开发效率
注意:
高并发服务,如秒杀系统、高性能API接口、RPC服务器,使用协程模式,服务的容错率会大大增加,某些接口出现故障时,不会导致整个服务崩溃。
爬虫,可实现非常巨大的并发能力,即使是非常慢速的网络环境,也可以高效地利用带宽。
即时通信服务,如IM聊天、游戏服务器、物联网、消息服务器等等,可以确保消息通信完全无阻塞,每个消息包均可即时地被处理。
Swoole
的协程在底层实现上是单线程的,因此同一时间只有一个协程在工作,协程的执行是串行的。这与线程不同,多个线程会被操作系统调度到多个CPU并行
执行。
一个协程正在运行时,其他协程会停止工作。当前协程执行阻塞IO
操作时会挂起,底层调度器会进入事件循环。当有IO
完成事件时,底层调度器恢复事件对应的协程的执行。
对CPU
多核的利用,仍然依赖于Swoole
引擎的多进程机制。
2.2 延迟收包
机制(setDefer
) 先来看下什么是发包,什么是收包。发包就是客户端与服务端建立连接,发送请求数据包,就是发包;收包就是客户端等待服务端处理,响应返回给客户端的过程,就是收包。
大多数情况下, [收包的耗时]要长一些,就是用户得到响应的时间会长一些,Swoole提供延迟收包的机制,使用setDefer()
方法声明延迟收包,可以将请求响应式的接口拆分为两个步骤,使用此机制可以实现先发送数据, 再并发收取响应结果。所以可以简单理解为defer模式下, 多个客户端的请求响应是并发。这样做的好处是,可以接收处理更多请求,程序非阻塞运行,大大提高了请求-响应性能。
以HttpClient
为例,设置setDefer(true)
后,发起$http->get()
请求,将不再等待服务器返回结果,而是在send request
之后,立即返回true
。在此之后可以继续发起其他HttpClient
、MySQL
、Redis
等请求。最后再使用$http->recv()
接收响应内容。
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、协程的注意问题
如果在多个协程间共用同一个协程客户端,同步阻塞程序不同,协程是并发处理请求的,因此同一时间可能会有很多个请求在并行处理,一旦共用客户端连接,就会导致不同协程之间发生数据错乱。
测试方法:以Redis操作,传统的同步阻塞方式、Swoole的协程的异步非阻塞方式这2种方式
1.传统的同步阻塞方式
执行:#php /www/redis.php
测试结果
10次访问Redis,耗时10.027s
2.Swoole的协程的异步非阻塞方式
执行:#php /www/redis.php
测试结果:10次访问Redis,耗时1.025s
测试报告分析:可以看出非常明显的差别,相同的访问操作,传统的同步阻塞方式耗时10s,Swoole的协程方式耗时仅1s,相差10倍,本人用协程mysql方式也做了测试,也是同样结果。可见用协程方式会大大提高性能,协程模式来改进优化我们原有的程序是可行的。
未完待续...