入门使用
swoole协程注意点:用PHP自带的sleep函数。阻塞的是PHP主进程,这时候的协程也停止运行。swoole协程超过5ms强制切换,防止进程饿死。swoole要用睡眠函数。用\Swoole\Coroutine::sleep(1);
go(function(){
for($i=0;$i<=10;$i++){
echo "\$i:".$i.PHP_EOL;
\Swoole\Coroutine::sleep(1);
}
for ($j=0;$j<=3;$j++){
echo "\$j:".$j.PHP_EOL;
\Swoole\Coroutine::sleep(1);
}
});
go(function(){
for($z=0;$z<=6;$z++){
echo "\$z:".$z.PHP_EOL;
\Swoole\Coroutine::sleep(1);
}
});
echo "协程测试".PHP_EOL;
协程修改外部的变量需要加引用符号
$count=0;
go(function()use(&$count){
for ($j=0;$j<=3;$j++){
$count=$count+$j;
\Swoole\Coroutine::sleep(1);
}
});
echo "协程测试:".$count.PHP_EOL;
协程之间的通信
这里没有使用sleep函数。使用sleep
函数底层会yield
当前协程,让出时间片,并添加一个异步定时器,当超时时间到达时重新resume
当前协程,恢复运行。
注意swoole4.4以后的版本写法。4.3和4.4不是一个版本
$chan=new co\Channel;
可以直接这么写。不用声明容量
//swoole4.4以后下面必须加上Swoole\Event::wait();
use Swoole\Coroutine as co;
$count=0;
$chan=new co\Channel(1);
$cid=go(function()use($chan,$count){ //只负责计算
for ($j=0;$j<=4;$j++){
if($j==2){
//push里面可以放入数字
$arr=$j;
//push里面也可以放数组
$arr=['arr'=>$j];
$chan->push($arr);
//让位代码执行权。时间片、yield让位的意思co::yield让出执行权。。$cid是协程id。
co::yield();
}
$count=$count+$j;
echo "co1=>count:".$count.",co2=>j:{$j}".PHP_EOL;
}
});
go(function()use($chan,$count,$cid){ //只负责输出
for ($j=0;$j>=3;$j++){
$count=$count+$j;
echo "co2=>count:".$count.PHP_EOL;
}
$pop=$chan->pop();
if(is_array($pop)){
$pop=json_encode($pop);
}
echo "协程测试:".$pop.PHP_EOL;
co::resume($cid); //恢复之前的携程
});
echo "携程结束".PHP_EOL;
//
//register_shutdown_function(function () {
// Swoole\Event::wait();
//});
//或者下面4.4必须这么执行
Swoole\Event::wait();
swoole中的Runtime
底层增加一个新的特性,可以在运行时动态将基于php_stream
实现的扩展、PHP网络客户端代码一键协程化。
文档传送门:https://wiki.swoole.com/wiki/page/p-runtime.html
如果不在swoole协程自带的列表中。使用协程会变成阻塞形式。
准备1.后台代码index.php
开启服务器
$t="Guest";
if(isset($_GET['t'])){
sleep(3);
$t=$_GET['t'];
}
$html=<<<data
this is server, user is:$t;
data;
exit($html);
访问代码
use Swoole\Coroutine as co;
function getHtml($t=false){
$url="http://localhost/";
if($t) $url.="?".$t;
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_HEADER,0);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
go(function(){
echo getHtml('t=启东').PHP_EOL;
});
go(function(){
echo getHtml().PHP_EOL;
});
结果、是阻塞的。
用socket客服端的方式(协程)
因为在支持里面有socket那么添加runtime参数
Swoole\Runtime::enableCoroutine(true);
function getHtml2($t=false){
$fp=stream_socket_client("tcp://127.0.0.1:81");
$path="/";
if($t) $path.="?".$t;
fwrite($fp, "GET $path HTTP/1.0\r\nAccept: */*\r\n\r\n");
$ret="";
while (!feof($fp)) {
$ret.=fgets($fp, 1024);
}
fclose($fp);
return $ret;
}
go(function(){
var_dump(getHtml2('t=启东'));
});
go(function(){
var_dump(getHtml2());
});
输出结果、先出Guest
。后输出了启东
。这里协程的作用就明显的突出来了
如果官方自带支持的协程则不用声明Swoole\Runtime::enableCoroutine(true);
use Swoole\Coroutine\Http\Client as httpClient;
function getHtml2($t=null){
$client=new httpClient('127.0.0.1', 81);
$client->get("/?$t");
echo $client->body;
$client->close();
}
go(function(){
getHtml2('t=启东');
});
go(function(){
getHtml2();
});
多协程控制顺序的基本方法
原来的协程代码。后面结果先输出了
use Swoole\Coroutine as co;
function query(array $sqls){
$mysql=new co\MySQL();
$conn=$mysql->connect(['host'=>"127.0.0.1",'user'=>"root"
,'password'=>123456,'database'=>'sk']);
foreach($sqls as $sql){
$statement=$mysql->prepare($sql);
$rows=$statement->execute();
foreach($rows as $row){
foreach ($row as $k=>$v){
echo $k."=>".$v.";";
}
}
}
echo PHP_EOL;
}
go(function(){
query(['select sleep(2);','select id,uname from sk_admin where id=1']);
});
go(function(){
query(['select id,uname from sk_admin where id=2']);
});
echo "done".PHP_EOL;
更改同步控制
go(function(){
$chan=new co\Channel(2);
go(function()use($chan){
query(['select id,uname from sk_admin where id=1']);
$chan->push(1);
});
go(function()use($chan){
query(['select id,uname from sk_admin where id=2']);
$chan->push(2);
});
for($i=0;$i<2;$i++){
$chan->pop();//会同步阻塞。
}
echo "done".PHP_EOL;
});
协程defer的使用
defer用于资源的释放, 会在协程关闭之前(即协程函数执行完毕时)进行调用, 就算抛出了异常, 已注册的defer也会被执行.
WaitGroup.php
use Swoole\Coroutine\Channel;
class WaitGroup{
private $chan;
private $count;
function __construct()
{
$this->chan=new Channel(1);
$this->count=0;
}
public function Add(int $c){
$this->count+=$c;
}
public function Done(){
$this->chan->push(1);
}
public function Wait(){
for($i=0;$i>$this->count;$i++){
$this->chan->pop();
}
}
}
go.php
<?php
//swoole4.3.6
require_once "WatiGroup.php";
require_once "vendor\autoload.php";
use Swoole\Coroutine as co;
function query(array $sqls){
$mysql=new co\MySQL();
$conn=$mysql->connect(['host'=>"192.168.123.196",'user'=>"root"
,'password'=>'123456','database'=>'sk']);
foreach($sqls as $sql){
$statement=$mysql->prepare($sql);
$rows=$statement->execute();
foreach($rows as $row){
foreach ($row as $k=>$v){
echo $k."=>".$v.";";
}
}
}
echo PHP_EOL;
}
go(function(){
$wg=new WaitGroup();
$wg->Add(2);//设置协程的数量
$logger=new Monolog\Logger('mysql');
go(function()use($wg,$logger){
defer(function ()use($wg,$logger) {
//用于释放协程资源。和异常补救测试。
$logger->info("延迟执行UID:". Co::getuid(),['id'=>1,'sleep'=>2]);
$wg->Done();
});
query(['select sleep(2)','select id,uname from sk_admin where id=1']);
throw new Exception("故意报出的异常、我要出现下面不执行");
echo "我不执行了";
});
go(function()use($wg,$logger){
query(['select id,uname from sk_admin where id=2']);
$logger->info("正常执行",['id'=>1,'sleep'=>0]);
$wg->Done();
});
$wg->Wait();
echo "done".PHP_EOL;
});
结果