我想咨询下协程的好处?

bobshipwood

问题描述

上诉是协程的用法:

$worker1 = new Worker('http://0.0.0.0:8001');
$worker1->eventLoop = Swoole::class; // 使用Swoole协程
$worker1->onMessage = function (TcpConnection $connection, Request $request) {
    Coroutine::create(function () {
        echo file_get_contents("http://www.example1.com/event/notify");
    });
    Coroutine::create(function () {
        echo file_get_contents("http://www.example2.com/event/notify");
    });
    $connection->send('ok');
};

但是我想了下,以下不用协程的场景有啥不同?

$worker1 = new Worker('http://0.0.0.0:8001');
$worker1->onMessage = function (TcpConnection $connection, Request $request) {
    Timer::add(0.001, function(){
        echo file_get_contents("http://www.example1.com/event/notify");
    })
    Timer::add(0.001, function(){
        echo file_get_contents("http://www.example1.com/event/notify");
    })
    $connection->send('ok');
};

里面都是耗时任务,timer::add也是异步的?但最终都能输出 $connection->send('ok');啊

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

找不到

606 7 2
7个回答

liziyu

There's no benefit, it's only a web app.

  • bobshipwood 12天前

    no benefit? even the first paragraph use coroutine, i thought it must be different between them.

nitron

你这段代码,体现不出来,当你把echo把file_get_content的值,放到connection->send里,就有区别了

  • bobshipwood 12天前

    你是说,协程版本真正做到并行处理,而timer版本还是串行?

  • MarkGo 7天前

    他的意思是当你要把执行结果send回客户端的时候,非协程版本你如何send呢?

nitron

严格意义还是串行,但在file_get_content的时候不会阻塞住当前进程,尤其是目标地址请求很慢的时候

  • bobshipwood 12天前

    假设file_get_contents()用时2秒。

    所以,用协成版本比timer版本要好,因为是相当于并行处理的,不会阻塞进程。所以整个完成所有任务的情况下,用时2秒

    但是timer版本是串行阻塞的。如果两个函数挨的特别近,且是0.001秒的情况下。完成这两个任务的,需要耗时2+2秒?

    我的理解正确吗?

JustForFun

……这是两段完全不一样的代码。

你应该对比的是以下两段代码,不完全一样,但基本行为差不多,不得不用delay,尽量类似吧,运行一下你就懂区别在哪了。

<?php

require_once __DIR__. '/vendor/autoload.php';
use Workerman\Worker;
use Workerman\Timer;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;

$worker1 = new Worker('http://0.0.0.0:8001');

$worker1->onMessage = function (TcpConnection $connection, Request $request) {
    Timer::delay(0.01, function () {
        sleep(2);
        echo "Hello world1\n". microtime(true). "\n";
    });

    Timer::delay(0.01, function () {
        sleep(2);
        echo "Hello world2\n". microtime(true). "\n";
    });

    $connection->send("ok\n");
};

Worker::runAll();
<?php

require_once __DIR__. '/vendor/autoload.php';
use Workerman\Worker;
use Workerman\Timer;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
use Workerman\Events\Swoole;
use Workerman\Coroutine;

$worker1 = new Worker('http://0.0.0.0:8002');
$worker1->eventLoop = Swoole::class; // 使用Swoole协程
$worker1->onMessage = function (TcpConnection $connection, Request $request) {
    Coroutine::create(function () {
        sleep(2);
        echo "Hello world1\n". microtime(true). "\n";
    });

    Coroutine::create(function () {
        sleep(2);
        echo "Hello world2\n". microtime(true). "\n";
    });

    $connection->send("ok\n");
};

Worker::runAll();
  • bobshipwood 12天前

    Timer有delay函数?

  • JustForFun 12天前

    你还是实际跑下代码吧。。。delay比协程更早有

  • bobshipwood 11天前

    不好意思,delay应该是0.001秒后,只执行一次的意思吧。,安装了swoole扩展后,和我想的差不多。

    我测试的结果:

    有协程版本,2秒内全部执行完毕。
    无协成版本,需要4秒,

北月妖王

Timer::add 本质上只是延时调度,不会让阻塞 IO 变成异步。
举个例子:

如果你用协程去请求两个接口,A 需要 2s,B 需要 3s,总耗时大约 3s,因为它们是并发的。

如果你用 Timer::add 去写这两个 file_get_contents,A 执行 2s 时整个进程都在等,B 要等 A 完成才开始跑 3s,总耗时就是 5s。

在低并发时你可能感觉不到差别,但一旦访问量上来,Timer 写法会让所有新请求都被阻塞,服务吞吐量急剧下降;而协程写法进程能挂起等待 IO,继续处理其他请求,性能差距会非常明显。

协程带来的「并发」不是多核并行,而是单线程里多个 I/O 任务能交替执行,充分利用 CPU 空闲时间。这和 Timer::add 最大的差别就在于:协程会在阻塞点自动让出执行权,而 Timer::add 还是会把进程卡死。

  • bobshipwood 11天前

    性能差距真的十分明显!!!

    如果能从event扩展和swoole扩展分别提供什么能力,为啥会这样的区别,可能更好理解

  • 北月妖王 11天前

    event 扩展只是一个底层事件循环,不会改变 PHP 函数的阻塞本质。
    swoole 扩展在事件循环之上加了协程调度和函数 Hook,让阻塞 I/O 可以异步化。
    所以差别就在于:event 只能“通知你有事件发生”,swoole 能“帮你把阻塞点挂起切走”,这是性能差异的根源。

  • bobshipwood 11天前

    所以,这两个扩展都需要安装吗?
    或者说,只装了swoole扩展,不装event扩展行不行?

  • 北月妖王 11天前

    ?????
    不需要两个扩展都装。event 只是提供事件循环,没协程能力;swoole 自带事件循环和协程调度,如果你要用 swoole 的协程,那么只装 swoole 就够了。

  • bobshipwood 11天前

    但是swoole的事件循环和event的事件循环是否是一致的?
    因为我怕不安装event的话,他底层无法使用epoll,并发能力可能又削弱了点

  • nitron 11天前

    用event就用php8的fiber,看文档吧,对协程这一块有说
    https://www.workerman.net/doc/webman/coroutine/coroutine.html
    另外,说实话,绝大多数应用还没到那种需要细抠event和swoole的性能差距的程度

  • bobshipwood 11天前

    好的,谢谢

MarkGo

比较常见的场景:
你是中间商,你只负责的信息流传递,如:
客户找你要报价,你此时会联系供应商获取报价,然后再告诉客户。

非协程环境下:
客户找你要报价,你让客户等着,然后你打给供应商,期间你一直在等待着供应商的回复,就算来了新的客户你也没法接待,直到供应商答复了你,你再告诉客户,这个时候你才空闲出来接待新的客户。

而协程环境下:
客户找你要报价,你让客户等着,然后你交代你同事A供应商回复了就告知这个客户,这时候来新客户,你就可以去接待这个新的客户了。

而你上面的例子有个误区,你并不需要执行回复客户这一步,所以在你看来两者没有区别。
*另外我没记错的话,webman的controller中,使用timer的话会导致进程同步在进行等待。

如上的场景,你=进程,你同事=协程,客户=客户端,供应商=通过IO请求的终端。
当然你会发现,通过webman的server.php配置,可以加大进程的数量,来满足上面的场景;
这个就是1.X的解决方案,
虽然解决了这个场景问题,但是带来的是性能开销成本的问题,因为新建一个进程消耗的资源要比新建一个协程的高。
不过你也可以通过增加硬件配置来抵消这部分性能的影响。

因此你可以发现,协程主要解决的并非加速客户端获取结果,而是使用更低的资源来处理更多的连接。
所以当你不需要同步返回处理结果,且处理过程中存在等待过长的IO操作(数据库读写/文件读写/网络请求...)时候,协程对你帮助不大。

  • 暂无评论
huazai

我理解的是,协程就是一个个任务块,需要绑定到一个线程上执行。传统的模型是,当前线程上如果有多个任务块,依次执行,当某一个任务块阻塞了。那必须等待这个阻塞的任务块结束了,才能继续下去执行。。而协程呢,就是当一个任务块阻塞了,就把这个任务块挂起,让内核进行监听阻塞并通知,IO多路复用(协程适合IO密集型,而不是计算密集型),从而让这个线程继续执行下面的任务块,不至于浪费CPU,避免了来回切换线程,等阻塞的任务块不阻塞了,在把这个任务块重新继续在线程上执行。

这个例子如果改成这样子,应该会直观些

$worker1 = new Worker('http://0.0.0.0:8001');
$worker1->eventLoop = Swoole::class; // 使用Swoole协程
$worker1->onMessage = function (TcpConnection $connection, Request $request) {
Coroutine::create(function () {
echo "协程执行--begin--0001";
sleep(5);
echo "协程执行--end--0001";
});
Coroutine::create(function () {
echo "协程执行--begin--0002";
echo "协程执行--end--0002";
});
};

这个例子,就是 0001 执行sleep的时候,自动切换到 0002去执行了,0002执行完毕了,可能在切回到0001。而timer就无法实现,timer是个定时器

  • 暂无评论
🔝