Coroutine concurrent scheduling
After learning the communication function Channel of the coroutine, we then talked about a WaitGroup function. In fact, it itself is a coroutine scheduling tool. We won't go into details about its function, after all, the previous article has made it very clear. Today, we will continue with the topic of WaitGroup and continue to talk about the concurrent scheduling of coroutines.
Coroutine execution and coroutine container
After learning this, I don't know if you have found a problem, that is, if it is not in the coroutine container, if a blocking operation is encountered, the coroutine is executed sequentially. And if it is in a coroutine container, it becomes concurrent execution.
go(function(){
sleep(2);
echo "cid1:" . Co::getCid() , PHP_EOL;
});
go(function(){
sleep(1);
echo "cid2:" . Co::getCid() , PHP_EOL;
});
//cid1:1
//cid2:2
In the above test code, the first coroutine rests for 2 seconds, the second rests for 1 second, and the final output results are executed sequentially. Next we put it into the coroutine container.
\Swoole\Coroutine\run(function(){
go(function(){
sleep(2);
echo "cid1:" . Co::getCid() , PHP_EOL;
});
go(function(){
sleep(1);
echo "cid2:" . Co::getCid() , PHP_EOL;
});
});
//cid2:3
//cid1:2
In the coroutine container, the second coroutine executes first and outputs the content. Obviously, this is a state very similar to parallel operation. So is that actually the case?
In fact, this part of the content should be explained in one-click coroutine, but I am afraid that smart friends may discover this problem in advance, so I will briefly say it here in advance.
In fact, coroutines are not parallel. I believe everyone already knows this. We have said it many times. Coroutines work on threads, and Swoole is a process with a thread. Coroutines do not have parallel capabilities, but are executed concurrently like functions. However, it is in user mode, and we can manually suspend and resume, which is the ability of yield() and resume() that we learned before. Therefore, this feature can be used to quickly switch to other coroutines for processing while IO is waiting, and then come back to continue processing the contents of this coroutine when the IO here ends.
The coroutine container actually implements a set of internal coroutine execution environment, so that many codes that were originally executed synchronously can be asynchronous in the container. We can also use co::sleep() outside the container to suspend concurrent operations, but inside the container, sleep() directly can take effect. In addition, in Swoole, sleep() is not a good thing. We may use it frequently for demonstration, but in real business development scenarios, it is best not to use it. For the reason, please refer to Swoole Programming Notes https://wiki.swoole.com/#/getting_started/notice?id=sleepusleep Impact .
We will talk about this in detail in the last one-click coroutine of the coroutine chapter. The content about parallelism and concurrency was also explained in the first article of the earliest process. If you don't remember, remember to go back and read it.
Simpler scheduling than WaitGroup
Since coroutines are used, we definitely need its concurrency capabilities, and concurrent operations may bring some problems. In fact, it is the result of the concurrent execution of multiple coroutines of a business that we talked about last time. At that time, we used WaitGroup to achieve the effect of waiting for multiple coroutines to complete the synchronous return. But in Swoole, a simpler tool called Barrier is also provided.
\Swoole\Coroutine\run(function () {
$time = microtime(true);
$barrier = \Swoole\Coroutine\Barrier::make();
foreach (range(1, 4) as $i) {
go(function () use ($barrier, $i) {
\Swoole\Coroutine\System::sleep($i);
});
}
\Swoole\Coroutine\Barrier::wait($barrier);
echo microtime(true) - $time, PHP_EOL;
});
// 4.0022649765015
In fact, it has the same properties as WaitGroup, but saves some steps. As you can see from the code, this tool component does not require our manual add() and done() . To use it, we just need to first make() an object, and then pass the object to the coroutine via use . As long as there is this Barrier object in use , it will start counting automatically. When the coroutine finishes executing, it will automatically done() . Finally, we can use Barrier's wait() method to wait and listen.
Does it feel more convenient than WaitGroup? If there are many coroutines, you can write a lot less add() and done() methods.
Coroutine application and scheduling on asynchronous server
We have always explained coroutines on the command line before, but in fact, it is used in the same way in server applications, and coroutines can also be scheduled in the same way.
$serv = new Swoole\Http\Server("0.0.0.0", 9501, SWOOLE_PROCESS);
$serv->on('request', function ($req, $resp) {
$time = microtime(true);
$wg = new \Swoole\Coroutine\WaitGroup();
$wg->add();
$wg->add();
$res = 1;
go(function () use ($wg, &$res) {
co::sleep(3);
$res += 1;
$wg->done();
});
go(function () use ($wg, &$res) {
co::sleep(4);
$res *= 10;
$wg->done();
});
$wg->wait();
$endTime = microtime(true) - $time;
$resp->end($res . " - " . $endTime);
});
$serv->start();
//20 - 4.002151966095
In the above paragraph, we are synchronously waiting through WaitGroup. You can try to replace it with Barrier to see the effect.
Summarize
Today's content is relatively simple. After all, after we have the previous foundation, it will become less and less difficult for us to learn and understand new concepts. The most important thing is to understand the concept of asynchronous execution and synchronous return, and understand why it is done. This piece of more information can be searched for the content related to Promise in JS mentioned earlier.
Test code:
https://github.com/zhangyue0503/swoole/blob/main/4.Swoole%E5%8D%8F%E7%A8%8B/source/4.5%E5%8D%8F%E7%A8%8B%E5% B9%B6%E5%8F%91%E8%B0%83%E5%BA%A6.php
Reference documentation:
https://wiki.swoole.com/#/coroutine/barrier
https://wiki.swoole.com/#/coroutine/multi_call?