关于 协程 概念的一些疑惑

查表仔

问题描述

作为一个php开发,平时接触最多的就是传统fpm框架(tp、laravel等),以及守护进程框架(webman等)。

关于协程的概念,目前看到 swoole、golang 中可以实现。对 协程 的概念有点模糊。

为此你搜索到了哪些方案及不适用的原因

关于 进程 的概念,无论是fpm,还是守护进程 workerman,都是一个进程处理一个请求,当 进程数量 处理不过来很多的请求的时候,会阻塞。

想知道协程这一块是怎么处理的?我有以下猜想:

举个例子,业务逻辑是这样的:

一个请求过来后,首先,需要 4 秒钟调用第三方接口A,需要 4 秒钟调用第三方接口B,拿到A和B接口返回的数据后,需要2秒钟进行A和B接口返回数据的组装。

我用同步处理这个场景,需要 4+4+2 = 10秒。如果我用协程,4+2=6秒 就可以完成。

在使用协程的情况下,如果我有5个进程,同时来了5个请求,单个进程里处理单个请求需要 6 秒钟。是不是这 5个进程可以同时生成5个协程来处理呢?还是说 5个进程,同一时间内,只能有一个协程在处理?

701 2 9
2个回答

chaz6chez

协程本身不具备并发能力,只是一种上下文(请求/响应/执行等)的编排方案,类似于队列;协程还区分有栈协程(PHP中的fiber)和无栈协程(PHP中的yield);协程一般需要结合线程或者异步执行能力才可以达到并发/并行效果。
你可以理解为,把代码段碎片化,按照协程调度器的执行方案进行执行。

协程 + 协程调度器 + 协程执行单元
消息 + 消息队列 + 消费者

看起来和队列很像是不是,本质上是一样的

  • 查表仔 19天前

    您上面回复的我看明白了,您回答的角度是从它本身以及和其它相关技术结合的 大面 来看的。我是有点纠结于他的实际流程是怎么执行的。是想知道在请求到达进程后,协程是如何处理以及挂起的,是单个进程之内只有一个协程在处理,还是所有进程之内只有一个协程在处理。

  • 查表仔 19天前

    如果按照您上面说的,是不是 golang 和 swoole的协程调度机制也不一样,那我上面的提问是不是得建立在某个技术栈上才能进行下一步的探讨

  • chaz6chez 19天前

    单个进程管理自己的协程,进程与进程之间是相互隔离的,所以Golang用的是多线程执行单元

  • chaz6chez 19天前

    是的,swoole是单个执行单元,golang是多个执行单元,PHP-fiber只能利用eventloop在主线程上进行切换调度

  • chaz6chez 19天前

    通常来说,和linux内核下面,进程切换的方案差不多,比如时间分片法,也就是某个执行体如果执行完了可以主动出让当前CPU,或者执行超时了以后被强制暂停,等到分配下一个CPU时间;

    也就是说,协程也可以这样

  • 查表仔 19天前

    感谢您的回答~

  • chaz6chez 19天前

    这个具体调度的实现,每种协程调度的具体细节可能都不同,取决于具体实现,但思路大差不差,可以关注一下;
    如果没记错的话,golang的协程和执行单元是m:n,java21和swoole的协程和执行单元是m:1;而php python等都是运用事件循环在当前主线程内进行调度的,与其他的用额外线程来执行的不一样。

  • 查表仔 19天前

    m:n 和 m:1 也是相当于单个进程内的协程调度规则把。在单个进程内,swoole的 m:1,有多个协程,但是每次只调度一个。例如我是多核cpu,一个核心能处理两个进程,但是这一个进程内,也就只能有1个协程在调度,虽然在很大程度上提高了请求的执行速度,但是其余空闲的核心就利用不上了。不如 golang的 m:n ,有多个协程,又能同时调度多个,从而充分利用了cpu的核心吧?
    我对操作系统进程线程调度这一块的知识掌握的太少了,得补习补习了

  • 查表仔 19天前

    我测试了swoole的协程,通过它提供的 WaitGroup 和 Barrier 这种方法,对于开发难度来说,感觉是比较好上手业务逻辑的。golang这种属于性能好,但是对于实际开发,如果不了解他的调度机制及实际顺序流程,感觉很容易写bug。

  • chaz6chez 19天前

    你可以这样理解,通常来说golang下是用线程比较多,一般一个服务启动一个进程即可,GPM模型最后会把碎片化的任务调度交给不同的线程来进行运行,这样可以利用多核;
    (如果没记错的话,你可以具体查看一下swoole文档或者源码)swoole/swow下除了php主线程之外还会有一个协程执行线程,会将主线程的任务碎片化调度交给执行线程进行执行,通常来说会在上层加上多进程模型来利用多核,也就是多个进程一托一;
    workerman如果使用fiber的话,就是主进程要处理碎片化调度和执行协程,从始至终只有一个线程进行执行,但是整体是多进程模型。
    进程与进程之间相互隔离,各是各的,整体看来都是可以利用多核的,只是利用的方式不一样;整体并没有说m:n就性能更好,因为线程和进程在内核中的调度也存在性能损耗,也会有内核态和用户态的切换等,各有优劣。

  • chaz6chez 19天前

    golang其实还好,也分systemcall和netpoll,不同场景下有的会在固定线程上执行,有的会有执行线程的转移切换,这个在调度器底层实现了,其实也不会有什么bug或者问题,对于开发者来说用起来跟m:1没什么区别;为什么实现m:1而不是m:n,可能更多的层面是因为php和java都存在虚拟机,另外生态上面的复用上,为了不对虚拟机进行大规模的重写,并利用之前的生态和模式(进程模型、bio等),从而用m:1的方式实现。
    多进程的资源占用上肯定是比多线程要多的,多方面考量吧

  • chaz6chez 19天前

    多进程模型的话,相当于自己的服务除了系统会对服务进行主进程的管理,自己的服务还需要在内部进行子进程管理;而多线程模型就仅仅只需要在进程内对线程管理而已,很多资源都可以复用,在界限上更符合“规范”或者“理念”;

  • chaz6chez 19天前

    进程之间肯定是没有线程之间那么方便快捷,存在一定的难度,为了简化这些内容,方便管理,把一些工作交给系统本身的能力,这样会更健壮,开发起来也不需要一些过分的奇淫技巧。但实际上来说,都能通过一些方法实现想要的功能。用一句比较通俗的话来说就是线程比进程更具有“边界感”。

  • 查表仔 19天前

    上面说的都很有道理,但我水平不够,对你上面说的部分知识点还是有点一知半解,再就是长期的开发思维都是对进程的一些操作,忽然理解起来线程的调度还是难懂。我边学边测试这边的知识点,在回头看看你上面说的,可能就好理解了,感谢您的耐心回答~

  • ikun 18天前

    好文 ,@chaz6chez 在论坛写个专栏吧 ORZ

  • chaz6chez 18天前

    @ikun 之后有空出一个吧,我最近在研究mmap和apcu,准备完善一下这个插件 https://www.workerman.net/plugin/133 ,让它支持更多的功能,因为我现在在计划做一个轻调度的插件,纯用内存和sqlite来支撑小型服务。

  • tomlibao 17天前

    @chaz6chez 厉害,你是主开发什么语言的?

  • chaz6chez 17天前

    @tomlibao 主PHP吧算是

  • 邬綵唔惪 16天前

    协程还区分有栈协程(PHP中的fiber)和无栈协程(PHP中的yield),这两种协程有啥区别呢?为啥流行不起来呢?

  • nitron 16天前

    有栈协程要保留函数调用栈用于挂起恢复,会需要更多的内存空间
    无栈协程在不改变函数调用栈的情况下,采用类似生成器的思路实现了上下文切换

    理论上无栈比有栈性能好,但实际使用中不需要扣资源的时候,两者没多大区别,有栈用起来方便点

  • 邬綵唔惪 16天前

    这两种协程为啥流行不起来呢?是因为需要改造生态的问题吗?

  • chaz6chez 15天前

    @邬綵唔惪 yield一直都流行,只不过圈子很小,reactphp、amphp这些都是利用yield + eventloop实现的类似async/await;这类组件或者框架有个缺点,不能利用php历史积攒下来的大部分组件包,因为整个思想是NIO的也就是no-blocking I/O,而PHP整个生态主要是围绕FPM,然后整体思想是blocking I/O的;同样,因为yield由于无栈,在框架层面实现时候很多东西没办法实现,所以这个圈子引入了fiber。

    还是那句话,协程本质上不具备并发能力,本质上是代码执行片段碎片化并编排的方案的一环,协程+协程调度器+协程执行单元才能实现具备高并发能力的方案;有的用线程,有的用事件驱动。

  • chaz6chez 15天前

    只不过很多人习惯把这种方案笼统的称之为“协程”

  • 邬綵唔惪 13天前

    fiber一样不能利用php历史积攒下来的大部分组件包吧?那样和用yield实现的有啥区别呢?引入的意思是啥呢?

he426100
  1. 同一时间内,只能有一个协程在处理?
    这句话指的是在一个进程内同一时间只有一个协程在处理,单个进程是可以创建无数个协程的,我试过在服务器上单进程创建1000万个定时器;

  2. 我用同步处理这个场景,需要 4+4+2 = 10秒。如果我用协程,4+2=6秒 就可以完成。
    用协程也是同步,只不过不会阻塞了,开N个Curl,效果是“并发”N个请求,实际上还是一个个去执行,只是发出请求后不会堵在那里等返回,跟队列是挺像的,把任务抛给队列,对当前业务流程来说就是秒完成;

  3. 同时来了5个请求
    协程的话单进程就可以处理了,不需要5个进程,fpm下一执行curl进程就堵在那里等返回,无法处理下一个请求,协程不存在的。

以上说的是swoole/swow

  • chaz6chez 15天前

    @he426100 swoole/swow有额外一条线程来专门执行协程的内容,不阻塞当前主线程,也就是有两条,你在主线程上创建的协程会被协程线程接管

  • chaz6chez 15天前

    你创建可以创建多个协程,但协程执行单元同一时间只有一个,通过合理的调度和主线程进行配合进行执行,从而达到高效的处理能力

🔝