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();

浙公网安备 33010602011771号