Swoole中的WaitGroup

WaitGroup的主要作用是什么呢?应该是用来管理多个协程,确保主协程等待所有子协程完成后再结束。这在处理并发任务时非常有用,比如同时发起多个http请求,然后等所有请求返回后再处理结果。

Swoole的协程是基于协程的轻量级线程,所以WaitGroup应该和协程配合使用。可能的方法包括add(),done()和wait()。add()用来增加等待的协程数,done()表示一个协程完成,减少计数,wait()则阻塞主协程直到计数器归零。

比如,创建WaitGroup实例,然后在启动每个协程前调用add(),可能参数是增加的数量,比如add(1)。然后在协程内部使用defer调用done(),确保即使协程出现异常也能执行done()。最后在主协程调用wait(),等待所有完成。

<?php
use Swoole\Coroutine;
use Swoole\Coroutine\WaitGroup;
Coroutine\run(function(){
    $wg = new WaitGroup();
    $wg->add(3);
    go(function()use($wg){
        Co::sleep(1);
        $wg->done();
    });
    go(function()use($wg){
        Co::sleep(1);
        $wg->done();
    });
    go(function()use($wg){
        Co::sleep(1);
        $wg->done();
    });
    $wg->wait();
    echo "ok";
});

上面这个例子是启动三个协程,每个协程执行一些任务,比如睡眠一段时间模拟耗时操作。主协程等待它们全部完成后再继续执行。不过要注意的是,add()应该在协程外部调用,否则可能出现竞态条件,比如在协程启动前计数器还没增加,主协程就已经开始等待了。另外,done()的次数必须和add()的次数一致,否则计数器可能变成负数,导致错误。

有没有可能发生协程还没add,就已经done得情况?比如在协程内add的话,如果协程先执行了done,而add还没调用,就会有问题,所有正确的做法是在启动协程之前先add,然后在协程结束时done.

另外Swoole的协程是协作式调度的,所以在协程中执行阻塞操作时会自动切换,但WaitGroup的机制应该和这个调度方式配合良好。

可能还需要注意异常处理,比如协程中发生异常,是否会导致done()没有被调用,进而导致主协程一致等待,这个时候应该用tr..finally或者defer中调用done(),确保无论协程是否成功完成,都会执行done().

还有一个问题是,如果add的数量超过实际done的次数,wait()会一直阻塞,反之如果done次数过多,会导致计数器变为负数,触发错误。所以在使用时需要严格匹配add和done的次数。

总结一下,Coroutine\WaitGroup的作用是同步多个协程,确保主协程等待所有子协程完成任务。正确使用方法是:在启动子协程前调用add()增加计数,每个子协程完成任务后调用done(),主协程调用wait()等待。同属需要注意异常处理和计数匹配,避免死锁和错误。

可能还需要比较WaitGroup和其他同步机制的区别,比如Channel。WaitGroup更适用于知道协程数量的情况,而Channel可以在不同协程间传递数据,或者用于位置数量的协程同步。

 关键注意事项

1.严格匹配计数

add()和done()必须成对出现,否则可能导致计数器为负(报错)或主协程永久阻塞。

2.异常处理

在子协程中使用try..finally或defer确保done()始终执行,避免因异常导致计数未减少。

3.避免竞态条件

add()必须在子协程启动前调用,防止子协程先执行done()导致计数器错误。

4.与Channel的区别

WaitGroup用于简单的同步等待。

Channel用于协程间通信或更复杂的同步逻辑。

适用场景

批量处理并发任务(如并发HTTP请求,数据库查询)

等待所有子任务完成后再进行结果聚合。

替代回调嵌套,提升异步代码可读性。

正确使用WaitGroup能显著简化协程同步逻辑,避免回调地狱。提升代码可维护性。

<?php
use Swoole\Coroutine\WaitGroup;

$server = new Swoole\Http\Server('0.0.0.0', 9501);

$server->on('request', function($req, $res) {
    $wg = new WaitGroup();
    $wg->add(2);
    $result = [];

    // 启动第一个协程
    go(function() use ($wg, &$result) {
        try {
            // 模拟任务
            $result['ccc'] = 1;
            Co::sleep(1); // 模拟异步操作
        } catch (Exception $e) {
            $result['ccc'] = 'Error: ' . $e->getMessage();
        } finally {
            $wg->done(); // 确保 done() 总是被调用
        }
    });

    // 启动第二个协程
    go(function() use ($wg, &$result) {
        try {
            // 模拟任务
            $result['bb'] = 1;
            Co::sleep(1); // 模拟异步操作
        } catch (Exception $e) {
            $result['bb'] = 'Error: ' . $e->getMessage();
        } finally {
            $wg->done(); // 确保 done() 总是被调用
        }
    });

    // 等待所有协程完成
    $wg->wait();

    // 返回响应
    $res->end(json_encode($result));
});

$server->start();

 

posted @ 2025-02-17 17:52  X__cicada  阅读(36)  评论(0)    收藏  举报