扩展视频https://www.bilibili.com/video/BV1oJ411U7bc/
1 第一集 进程(Process)、线程(thread)、协程(Coroutine)
视频地址 https://edu.51cto.com/center/course/lesson/index?id=412750
IO 密集型服务的瓶颈不在 CPU 处理速度,而在于尽可能快速的完成高并发、多连接下的数据读写。
总结
- 协程(Coroutine)是纯用户态的线程
- 传统线程:cpu抢占式调度
- 协程:由swoole调度 创建和切换消耗更低
- 线程是cpu调度的最小单位
- 线程之间可以共享资源
1.1 Master:
处理核心事件驱动(主进程)
1.2 Reactor:
处理TCP连接,收发数据的线程,Swoole的主线程在Accept新的连接后,会将这个连接分配给一个固定的Reactor线程,并由这个线程负责监听此socket,在socket可读时读取数据,并进行协议解析,将请求投递到Worker进程,在数据可写时将数据发送给TCP客户端;
a:负责维护客户端TCP连接,处理网络IO,处理协议,收发数据
b:完全是异步非阻塞的模式
c:全部为C代码,除了Start\Shudown事件回调外,不执行任何PHP代码
d:将TCP客户端发来的数据缓存,拼接,拆分成完整的一个请求数据包
e:Reactor以多线程的方式运行
1.3 Manager进程:
a:swoole中worker/task进程都是由Manager进程Fork并管理的;
b:子进程结束运行时,manager进程负责回收此子进程,避免成为僵尸进程,并创建新的子进程;
c:服务器关闭时,manager进程将发送信号给所有的子进程,通知子进程关闭服务
d:服务器reload时,manager进程会逐个关闭/重启子进程
1.4 :Worker进程:
处理客户端的请求
a:接收由Reactor线程投递的请求数据包,并执行PHP回调函数处理的数据
b:生成响应数据并发给Reactor线程,由Reactor线程发送给TCP客户端;
c:可以是异步非阻塞模式,也可以是同步阻塞模式
d:Worker以多进程的方式运行;
1.5 Task进程:
异步工作进程
a:接收由Worker进程通过swoole_server->task/taskwait方法投递的任务;
b:处理任务,并将结果数据返回(swoole_server->finsish)给Worker进程;
c:完全是同步阻塞模式
d:TaskWorker以多进程的方式运行:
Reactor,Worker,TaskWorker的关系
a:可以理解为Reactor就是nginx,Worker就是php-fpm,Reactor线程异步并行的处理网络请求,然后再转发给Worker进程中去处理,Reactor和Worker间通过UnixSocket进行通信;
b:在php-fpm的应用中,经常会将一个任务异步投递到Redis等队列中,并在后台启动一些php进程异步的去处理这些任务,Swoole提供的的Worker是一套更完整的方案,将任务的投递,队列,php任务处理进程管理合为一体,通过底层提供的API可以非常简单地实现异步任务的处理,另外
TaskWorker还可以在任务执行完成后,再返回一个结果反馈到Worker.
c:Swoole的Reactor,Worker,TaskWorker之间可以紧密的联系起来,提供更高级的使用方式;
d:一个更通俗的的比喻,假设Server是一个工厂,那Reactor就是销售,接收客户订单,而Worker就是工厂,当销售接到订单后,Worker去工作生产出客户要的东西,而TaskWorker可以理解为行政人员,可以帮助Worker干些杂事,让Worker专心工作;
e:底层会为Worker进程,TaskWorker进程分配一个唯一的ID;
f:不同的Worker和Task进程之间可以通过sendMessage接口进行通信
1.6 Task进程
a:Task和Worker进程的通信通过unix socket进行
b:Task数据传递大小小于8K直接管道传递,大于8K写入临时文件传递
c:Task传递对象
(1):可以通过序列化传递一个对象的拷贝(非应用)
(2):Task对象的改变不会反应到worker中(两个进程中是独立的)
(3):数据库连接,网络连接对象不可传递
d:Task的Onfinish的回调会传递给调用该task方法的worker进程(原路返回可寻)
e:Task用途:模拟Mysql连接池
1.7 Timer定时器
a:Timer基于epoll的超时机制实现
b:使用堆存放Timer,提高检索效率
2 开发环境的配置以及第一段协程代码
大家可以自行搭建,也可以使用我这里推荐的一个学习环境镜像
https://hub.docker.com/r/shenyisyn/swoole4.2.9/
基于php7.2 alpine镜像,swoole 4.2.9
https://www.cnblogs.com/pizixu/articles/12001717.html
2.1 go函数 创建协程
创建协程 可以用 Coroutine::create或go (推荐后者,代码更简洁)
譬如有两个任务
1、从 1 输出到5 。每隔一秒输出
2、从 6输出到10,每隔一秒输出
常规写法::只能按顺序进行运行(相当于单进程 单线程里运行)

<?php for ($i=1;$i<=5;$i++){ echo $i.PHP_EOL; sleep(1); } for ($i=6;$i<=10;$i++){ echo $i.PHP_EOL; sleep(1); } echo '协程测试1';
协程方式: (可以交叉进行运行,一个协程执行一部分的时候进行协程的切换,协程的调度是由swoole来完成的,而不是CPU来抢占式的来调度)(协程怎么知道什么时候切换回来?:事件驱动https://www.cnblogs.com/staff/p/9709970.html)
<?php go(function(){ for ($i=1;$i<=5;$i++){ echo $i.PHP_EOL; //相当于 PHP 的 sleep 函数, //不同的是 Coroutine::sleep 是协程调度器实现的, //底层会 yield 当前协程,让出时间片,并添加一个异步定时器,当超时时间到达时重新 resume 当前协程,恢复运行。 Swoole\Coroutine::sleep(1); } }); go(function (){ for ($i=6;$i<=10;$i++){ echo $i.PHP_EOL; Swoole\Coroutine::sleep(1); } }); echo '协程测试2'.PHP_EOL;
3 协程通信之channel入门
https://www.cnblogs.com/pizixu/articles/12002639.html
3.1 协程的通信
我们在实际场景进行相关业务开发的时候,很可能协程之间并不是独立存在的,而是有相关业务纠缠的,这里就需要用到了协程的通信;
3.1.1 单个协程
\seckill\swoolepro\class2.php
3.1,1.1 不用引用地址

<?php //协程的通信 $count = 0; go(function () use ($count){ for ($i=1;$i<=3;$i++){ $count ++; echo $count.PHP_EOL; } }); echo '单个协程'.$count.PHP_EOL;

<?php //协程的通信 $count = 0; go(function () use (&$count){ for ($i=1;$i<=3;$i++){ $count ++; echo $count.PHP_EOL; } }); echo '单个协程'.$count.PHP_EOL;
3.1.1.2 取地址就可以通信
一旦使用了引用就会重新开辟一个内存空间进行传址指向该引用的值;
3.1.2 两个协程
3.1.2.1 第一个协程使用引用,第二个不使用
<?php //协程的通信 $count = 0; //第一个协程使用了取地址 go(function () use (&$count){ for ($i=1;$i<=3;$i++){ $count ++; echo $count.PHP_EOL; } }); //第2个协程不使用取地址 go(function () use ($count){ echo '两个协程'.$count.PHP_EOL; });
可见这两个协程没有切换,而是第一个执行完。然后顺序执行了第二个
3.1.2.2 两个协程都用引用

<?php //协程的通信 $count = 0; // 两个协程都用引用 go(function () use (&$count){ for ($i=1;$i<=3;$i++){ $count ++; echo $count.PHP_EOL; } }); // 两个协程都用引用 go(function () use (&$count){ echo '两个协程'.$count.PHP_EOL; }); //结果和上面一样
两个协程没有切换
3.1.2.3 两个协程都不使用引用

<?php //协程的通信 $count = 0; // 两个协程都不使用引用 go(function () use ($count){ for ($i=1;$i<=3;$i++){ $count ++; echo $count.PHP_EOL; } }); // 两个协程都不使用引用 go(function () use ($count){ echo '两个协程'.$count.PHP_EOL; }); //也不进行协程切换。但第二个变量取主进程的变量值 //而不是第一个协程执行完以后的变量值
两个协程没有切换
3.1.2.4 第一个协程不使用,第二个使用的情况

<?php //协程的通信 $count = 0; // 第一个协程不使用 go(function () use ($count){ for ($i=1;$i<=3;$i++){ $count ++; echo $count.PHP_EOL; } }); // 第二个使用引用 go(function () use (&$count){ echo '两个协程'.$count.PHP_EOL; }); //也不进行协程切换。但第二个变量取主进程的变量值 //而不是第一个协程执行完以后的变量值
结果和上面一样 两个协程没有切换
3.1.3 两个协程的切换
3.1.3.1 第一个协程使用引用,第二个不使用

<?php //协程的通信 $count = 0; // 第一个协程使用引用 go(function () use (&$count){ for ($i=1;$i<=3;$i++){ $count ++; echo $count.PHP_EOL; //Swoole\Coroutine::sleep(1); //简写方法 Co::sleep(1); } }); // 第二个不使用 go(function () use ($count){ echo '两个协程的切换'.$count.PHP_EOL; });

3.1.3.2 两个协程都用引用

<?php //协程的通信 $count = 0; // 两个协程都用引用 go(function () use (&$count){ for ($i=1;$i<=3;$i++){ $count ++; echo $count.PHP_EOL; //Swoole\Coroutine::sleep(1); //简写方法 Co::sleep(1); } }); // 两个协程都用引用 go(function () use (&$count){ echo '两个协程的切换'.$count.PHP_EOL; });
结果和上面一样
3.1.3.3 两个协程都不使用引用

<?php //协程的通信 $count = 0; // 两个协程都不使用引用 go(function () use ($count){ for ($i=1;$i<=3;$i++){ $count ++; echo $count.PHP_EOL; //Swoole\Coroutine::sleep(1); //简写方法 Co::sleep(1); } }); // 两个协程都不使用引用 go(function () use ($count){ echo '两个协程的切换'.$count.PHP_EOL; });
3.1.3.4 第一个协程不使用,第二个使用的情况

<?php //协程的通信 $count = 0; // 第一个协程不使用 go(function () use ($count){ for ($i=1;$i<=3;$i++){ $count ++; echo $count.PHP_EOL; //Swoole\Coroutine::sleep(1); //简写方法 Co::sleep(1); } }); // ,第二个使用的情况 go(function () use (&$count){ echo '两个协程的切换'.$count.PHP_EOL; });
3.2 使用Coroutine\Channel (通道)进行协程的通讯
3.2.1 swoole中协程之间通讯的编程范式
- 协程内部禁止使用全局变量和静态变量
- 协程使用use关键字引用外部变量到当前作用域的时候禁止使用引用
- 协程之间的通讯必须使用Channel
- 与xdebug、xhprof、blackfire等zend扩展不兼容,例如不能使用xhprof对协程server进行性能分析采样
3.2.2 使用通道
<?php //使用Coroutine\Channel形成协程的通讯 use Swoole\Coroutine as co; //创建一个容量为1的通道 $chan = new co\Channel('1'); // 第一个协程 只负责计算(cpu) go(function () use ($chan){ $count = 0; for ($i=1;$i<=3;$i++){ $count ++; echo $count.PHP_EOL; //Swoole\Coroutine::sleep(1); //简写方法 Co::sleep(1); //每当遇到sleep时会经行协程的切换 } $chan->push($count); echo '协程1已经发送数据到协程2'.PHP_EOL; }); // 第二个协程 只负责输出 (IO) go(function () use ($chan){ echo '协程2输出数据:'.$chan->pop().PHP_EOL; });
如果 没有 $chan->push($count);这行
程序会卡在pop()
4 协程的切换和协程的挂起
https://www.cnblogs.com/pizixu/articles/12002811.html
4.1 协程的切换

<?php //使用 sleep模拟IO阻塞,因此会引起协程的切换(进入调度队列) //原理如下 //https://wiki.swoole.com/wiki/page/784.html go(function (){ for ($i=1;$i<=10;$i++){ echo $i.PHP_EOL; Co::sleep(0.5); } }); go(function (){ for ($i=1;$i<=1000;$i++){ echo 'a'.$i.PHP_EOL; } });
。。。。
第一个协程输出1后,直接切换到第二个协程,直到第二个协程在0.5秒内执行完毕,再切换到第一个协程
4.2 协程的挂起yield与恢复resume
<?php use Swoole\Coroutine as co; // $cid 协程的id $cid = go(function (){ for ($i=1;$i<=10;$i++){ if ($i==5){ //让出当前协程的执行权。 而不是基于 IO 的协程调度 //此方法拥有另外一个别名:Coroutine::suspend() //必须与Coroutine::resume()方法成对使用。该协程yield以后,必须由其他外部协程resume,否则将会造成协程泄漏,被挂起的协程永远不会执行。 co::yield(); } echo $i.PHP_EOL; } }); go(function () use ($cid){ for ($i=1;$i<=1000;$i++){ echo 'b'.$i.PHP_EOL; } //手动恢复某个协程,使其继续运行,不是基于 IO 的协程调度 co::resume($cid); });
。。。。
5 协程方式请求网页:curl、stream_socket和swoole协程客户端 对比 关于Runtime
https://www.cnblogs.com/pizixu/articles/12011479.html
5.1 curl或file_get_content等
<?php //cURL //不再会自动兼容协程内外环境,一旦开启,则一切阻塞操作必须在协程内调用 ( \Swoole\Runtime::enableCoroutine(true); function getHtml($t=false) { if ($t){ sleep(3); } $url = 'https://www.showapi.com/apiGateway/generateSignUrl?showapi_apiCode=1196'; $ch = curl_init() ; curl_setopt($ch,CURLOPT_URL,$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('aaa').PHP_EOL; echo "协程1".PHP_EOL; }); go(function (){ echo getHtml().PHP_EOL; echo "协程2".PHP_EOL; });
让原来的同步 IO 的代码变成可以协程调度的异步 IO,即一键协程化
。
如果不加\Swoole\Runtime::enableCoroutine(true); 则会变
5.2 stream_socket
用于流媒体
https://blog.csdn.net/m0_68949064/article/details/124568417
https://wiki.swoole.com/#/runtime?id=%e5%b8%b8%e8%a7%81%e7%9a%84hook%e5%88%97%e8%a1%a8
5.3 Coroutine\Http\Client
协程 HTTP/WebSocket 客户端
<?php use Swoole\Coroutine\Http\Client as httpClient; //https://wiki.swoole.com/#/coroutine_client/client go(function (){ $client = new httpClient('www.douban.com','80') ; $client->get('/404'); echo $client->body.PHP_EOL; echo '以上协程1豆瓣'.PHP_EOL; $client->close(); }); go(function (){ $client = new httpClient('www.baidu.com','80') ; $client->get("/404"); echo $client->body.PHP_EOL; echo '以上协程2百度'.PHP_EOL; $client->close(); });
可见百度比豆瓣服务器更快
6 Mysql 协程
https://www.cnblogs.com/pizixu/articles/12015989.html
6.1 方式1 开启一键协程(不推荐)
<?php //开启一键协程化 \Swoole\Runtime::enableCoroutine(true); $dsn = "mysql:host=10.10.10.114;dbname=test"; go(function() use($dsn){ $pdo = new PDO($dsn,'ttt','123456'); $stat = $pdo->query("select sleep(3); select * from users where id=1;"); $stat->setFetchMode(PDO::FETCH_ASSOC); $stat->nextRowset(); //获得下一个结果集 https://www.imooc.com/video/3016 $rows = $stat->fetchAll(); var_dump($rows); $pdo = null; }); go(function() use($dsn){ $pdo = new PDO($dsn,'ttt','123456'); $stat = $pdo->query("select * from users where id=2;"); $stat->setFetchMode(PDO::FETCH_ASSOC); $rows = $stat->fetchAll(); var_dump($rows); $pdo = null; });
如果不开启一键协程
6.2 方式2 使用mysql协程客户端(推荐)
<?php //Mysql协程客户端 use Swoole\Coroutine as co; go(function (){ $mysql = new co\MySQL(); $mysql->connect([ 'host'=>'10.10.10.114', 'user'=>'ttt', 'password'=>'123456', 'database'=>'test' ]); $row1 = $mysql->query("select sleep(3);"); $row2 = $mysql->query("select * from users where id=1"); var_dump($row2); }); go(function (){ $mysql = new co\MySQL(); $mysql->connect([ 'host'=>'10.10.10.114', 'user'=>'ttt', 'password'=>'123456', 'database'=>'test' ]); $row2 = $mysql->query("select * from users where id=2"); var_dump($row2); });
7 第7集 使用Channel多协程运行顺序控制的基本方法
https://edu.51cto.com/center/course/lesson/index?id=412744
https://wiki.swoole.com/#/coroutine?id=%e4%bb%80%e4%b9%88%e6%98%afchannel
seckill\swoolepro\class7.php
7.1 未使用channel

<?php use Swoole\Coroutine as co; function query(array $sqls){ $mysql = new co\MySQL(); $config = array( 'host'=>'10.10.10.114', 'user'=>'ttt', 'password'=>'123456', 'database'=>'test' ); $dsn = "mysql:host=10.10.10.114;dbname=test"; $pdo = new \PDO($dsn,'ttt','123456'); $conn = $mysql->connect($config); foreach ($sqls as $sql){ $statement = $mysql->prepare($sql); $rows = $statement->execute(); var_dump($rows); } echo PHP_EOL; } go(function (){ query(["select sleep(2);","select * from users where id=1",]); }); go(function (){ query(["select * from users where id=2"]); }); echo 'done'.PHP_EOL;

执行顺序 1 done
2 id=2
3 卡2秒
4 id=1
7.2 使用channel后
<?php use Swoole\Coroutine as co; function query(array $sqls){ $mysql = new co\MySQL(); $config = array( 'host'=>'10.10.10.114', 'user'=>'ttt', 'password'=>'123456', 'database'=>'test' ); $conn = $mysql->connect($config); foreach ($sqls as $sql){ $statement = $mysql->prepare($sql); $rows = $statement->execute(); var_dump($rows); } echo PHP_EOL; } go(function (){ //https://wiki.swoole.com/wiki/page/p-channel.html //Channel可用于多进程环境下,底层在读取写入时会自动加锁,应用层不需要担心数据同步问题 $chan = new co\Channel(2);//2代表两个协程 go(function () use ($chan){ query(["select sleep(3);","select * from users where id=1",]); $chan->push(1); }); go(function () use ($chan){ query(["select * from users where id=2"]); $chan->push(2); }); for ($i=0;$i<2;$i++){ $chan->pop(); } echo 'done'.PHP_EOL; });
done到最后来了
只要没有push进来,pop就会阻塞
8 模拟go语言的waitGroup控制协程的运行顺序
https://www.cnblogs.com/pizixu/articles/12027825.html
8.1 创建waitGroup
8.1.1 首先创建一个文件夹
app ---sync
--WaitGroup.php
8.1.2 终端执行 composer init
自动生成composer.json文件
8.1.3 在生成的composer.json中加入如下代码
"autoload": { "psr-4": { "App\\": "app/" } }
8.1.4:执行composer dump-autoload
8.1.5 将生成的vendor目录手动上传到服务器
8.1.6 WaitGroup中代码
<?php namespace App\sync; use Swoole\Coroutine\Channel; //channel控制协程的运行顺序 class WaitGroup { private $chan; //通道 private $count; //协程数量 function __construct() { $this->chan = new Channel(1); //初始化容量为1 可以通过Done修改 $this->count = 0; } //设置协程的数量 public function Add(int $coroutineCount) { $this->count+=$coroutineCount; } public function Done() { //写入一个数据 $this->chan->push(1); //随便加一个数 } //Add-> Done...-> Wait的顺序 public function Wait() { for ($i=0;$i<$this->count;$i++){ $this->chan->pop(); } } }
8.1.7 class8.php中的代码
<?php require_once "vendor/autoload.php"; use App\sync\WaitGroup; use Swoole\Coroutine as co; function query(array $sqls){ $mysql=new co\MySQL(); $conn=$mysql->connect([ 'host'=>'10.10.10.114', 'user'=>'ttt', 'password'=>'123456', 'database'=>'test' ]); foreach($sqls as $sql){ $statement=$mysql->prepare($sql); if ($statement == false) { var_dump($mysql->errno); var_dump($mysql->error); } $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);//设置协程的数量 //协程1 go(function() use($wg){ query(["select sleep(3)","select * from users where id=1"]); $wg->Done(); }); //协程2 go(function() use($wg){ query(["select * from users where id=2"]); $wg->Done(); }); $wg->Wait(); //协程3 go(function (){ echo "我是被阻塞的协程3".PHP_EOL; }); echo "done".PHP_EOL; });
9 引入第三方日志库monolog与go语言风格的defer使用
9.1 引入一个第三方日志库:monolog
为了方便演示 今天的功能,我们引入一个第三方日志库:monolog
链接网址:https://github.com/Seldaek/monolog
这是一个目前在PHP中比较流行的日志库,可以非常方便把日志写入到控制台,文件,redis,elasticsearch等
composer require monolog/monolog
如果不行 就切换到阿里云镜像 composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
然后把vendor目录手动上传
9.2 go语言风格的defer使用
Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。
<?php require_once "vendor/autoload.php"; use App\sync\WaitGroup; use Swoole\Coroutine as co; use Monolog\Logger; use Monolog\Handler\StreamHandler; //use Monolog\Handler\FirePHPHandler; function query(array $sqls) { $mysql = new co\MySQL(); $conn = $mysql->connect([ 'host'=>'10.10.10.114', 'user'=>'ttt', 'password'=>'123456', 'database'=>'test' ]); foreach ($sqls as $sql) { $statement = $mysql->prepare($sql); if ($statement == false) { var_dump($mysql->errno, $mysql->error); } $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 Logger("mylog"); //https://www.cnblogs.com/i6010/articles/13100810.html //StreamHandler:把记录写进PHP流,主要用于日志文件。 //INFO (200): 关键事件。 $logger->pushHandler(new StreamHandler(__DIR__.'/my_info.log', Logger::INFO)); //$logger->pushHandler(new FirePHPHandler()); //可以设置多个多种方式的Handler //WARNING (300): 出现非错误的异常。 $logger->pushHandler(new StreamHandler(__DIR__.'/my_error.log', Logger::ERROR)); //var_dump($logger); go(function () use ($wg,$logger) { query(["select sleep(3)", "select * from users where id=1"]); //先被 defer 的语句最后被执行 defer(function ()use ($wg,$logger) { $logger->info("延迟执行1(先被 defer 的语句最后被执行)",["uid"=>1,"sleep"=>3]); $wg->Done(); }); defer(function ()use($logger){ $logger->info("延迟执行3"); echo "延迟执行3".PHP_EOL; }); $logger->error("未拦截异常"); $logger->notice("未拦截异常"); $logger->warning("未拦截异常"); throw new Exception("未拦截异常"); }); go(function () use ($wg,$logger) { query(["select * from users where id=2"]); $logger->info("延迟执行2:没有用defer,第一个被记录",["uid"=>2,"sleep"=>0]); $wg->Done(); }); $wg->Wait(); echo "done" . PHP_EOL; });
$logger->error("未拦截异常");
$logger->notice("未拦截异常");
$logger->warning("未拦截异常");
10 第10集 使用协程实现一个简单问答(判断题)
https://edu.51cto.com/center/course/lesson/index?id=412741

<?php //使用协程实现一个简单的问答(判断题) use Swoole\Coroutine as co; $ques = [ 'PHP是不是最好的语言?'=>1, '996恶心吗?'=>1 , '加班是一种福气吗?'=>0 ]; /*foreach ($ques as $que=>$ans){ echo $que.PHP_EOL; $get = fgets(STDIN); if ($get == $ans){ echo '正确'.PHP_EOL; }else{ echo '错误'.PHP_EOL; } }*/ $chan = new co\Channel(); go(function () use ($ques,$chan){ $ques['end']=1; foreach ($ques as $que=>$ans){ $chan->push($que); } }); go(function () use ($ques,$chan){ while (true){ $getQue = $chan->pop(); if ($getQue=='end'){ break; } echo $getQue.PHP_EOL; $getAns = fgets(STDIN); if($getAns==$ques[$getQue]){ echo '正确'.PHP_EOL; }else{ echo '错误'.PHP_EOL; } } });
11 使用协程实现一个简单问答(答完再计算成绩)
自己写的 不一定对
<?php //使用协程实现一个简单的问答(判断题) use Swoole\Coroutine as co; require_once "vendor/autoload.php"; $ques = [ 'PHP是不是最好的语言?'=>1, '996恶心吗?'=>1 , '加班是一种福气吗?'=>0 ]; /*foreach ($ques as $que=>$ans){ echo $que.PHP_EOL; $get = fgets(STDIN); if ($get == $ans){ echo '正确'.PHP_EOL; }else{ echo '错误'.PHP_EOL; } }*/ $chan = new co\Channel(); go(function () use ($ques,$chan){ $ques['end']=1; foreach ($ques as $que=>$ans){ $chan->push($que); } }); $count = 0; go(function () use ($ques,$chan,$count){ while (true){ $getQue = $chan->pop(); if ($getQue=='end'){ break; } echo $getQue.PHP_EOL; $getAns = fgets(STDIN); if($getAns==$ques[$getQue]){ //echo '正确'.PHP_EOL; $count ++; }else{ //echo '错误'.PHP_EOL; } } defer(function ()use($count){ echo '得分'.$count.PHP_EOL; }); });

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
2019-09-29 云开发 小程序的审核和上线