CPU一次只能对一个进程做处理。
进程有自己独立的地址空间,是资源分配和拥有的单位。进程之间数据不共享,进程内至少有一个线程。
线程是处理器调度的最小单位,是进程内的一个执行单元,同一个进程内的线程共享进程的地址空间和资源。
协程的本质是将线程的调度权还给了程序本身,而不是CPU。所以协程也称用户态的轻量型线程。
以官方文档为主,地址点我
创建一个新的协程,并立即执行。1
2function SwooleCoroutine::create(callable $function, ...$args) : int|false;
function go(callable $function, ...$args) : int|false; // 短名API
$function 协程执行的代码,必须为callable,系统能创建的协程总数量受限于server->max_coroutine设置。创建失败返回false,创建成功返回协程的ID。
CoroutineSystem::sleep
进入等待状态。相当于PHP的sleep函数,不同的是Coroutine::sleep是协程调度器实现的,底层会yield当前协程,让出时间片,并添加一个异步定时器,当超时时间到达时重新resume当前协程,恢复运行。使用sleep接口可以方便地实现超时等待功能。使用sleep模拟IO阻塞,会引起协程的切换(进入调度队列)1function CoroutineSystem::sleep(float $seconds);
$seconds为睡眠的时间,单位为秒,支持浮点型,最小精度为毫秒(0.001秒)。必须大于0,最大不得超过一天时间(86400秒)
CoroutineChannel
通道,类似于go语言的chan,支持多生产者协程和多消费者协程。底层自动实现了协程的切换和调度。
Channel->push 当队列中有其他协程正在等待pop数据时,自动按顺序唤醒一个消费者协程。当队列已满时自动yield让出控制器,等待其他协程消费数据
Channel->pop 当队列为空时自动yield,等待其他协程生产数据。消费数据后,队列可写入新的数据,自动按顺序唤醒一个生产者协程。
简单代码示例如下1
2
3
4
5
6
7
8
9
10
11
12
13use SwooleCoroutine as co;
$chan=new coChannel(1); // 容量为1 最小为1
go(function() use($chan){ //只负责 计算 cpu
$count=0;
for($i=1;$i<=3;$i++){
$count=$count+$i;
co::sleep(1);
}
$chan->push($count);
});
go(function() use($chan){ //只负责输出 IO
echo $chan->pop().PHP_EOL;
});
Coroutine::yield
让出当前协程的执行权。 此方法拥有另外一个别名:Coroutine::suspend()1function Coroutine::yield();
必须与Coroutine::resume()方法成对使用。该协程yield以后,必须由其他外部协程resume,否则将会造成协程泄漏,被挂起的协程永远不会执行。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17use SwooleCoroutine as co;
$cid = go(function(){
for($i=1;$i<=10;$i++){
if($i==6){
co::yield();
}
echo $i.PHP_EOL;
// co::sleep(0.2); // 当IO存在堵塞时发生协程切换 进入调度队列 没有堵塞的话看起来是顺序执行
}
});
go(function() use ($cid){
for($i=1;$i<=10;$i++){
echo 'a'.$i.PHP_EOL;
}
co::resume($cid);
});
CoroutineHttpClient
协程版Http客户端基于原生的异步Http客户端,基本的设置和使用方法和异步Http客户端一致,不在需要注册回调函数,只需要同步写法即可1
2
3
4
5
6
7
8
9
10
11
12go(function(){
$cli = new SwooleCoroutineHttpClient('192.168.2.156', 8082);
$cli->get('/index.php?t=123123');
echo $cli->body;
$cli->close();
});
go(function(){
$cli = new SwooleCoroutineHttpClient('192.168.2.156', 8082);
$cli->get('/index.php');
echo $cli->body;
$cli->close();
});
如果未设置timeout,则将底层connect和request超时设置为默认的500ms
Runtime
在4.1.0版本中,底层增加一个新的特性,可以在运行时动态将基于php_stream实现的扩展、PHP网络客户端代码一键协程化。1
2function Runtime::enableCoroutine(bool $enable = true, int $flags = SWOOLE_HOOK_ALL);
function Runtime::enableCoroutine(int $flags = SWOOLE_HOOK_ALL);
$enable:打开或关闭协程 $flags:选择要Hook的类型,可以多选,默认为全选。仅在$enable = true时有效
需要留意官方文档中的可用列表和不可用列表1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23SwooleRuntime::enableCoroutine();
function getHtml2($t=false){
$fp=stream_socket_client("tcp://192.168.2.156:8082");
$path="/index.php";
if($t) $path.="?".$t;
fwrite($fp, "GET $path HTTP/1.0rnAccept: */*rnrn");
$ret="";
while (!feof($fp)) {
$ret.=fgets($fp, 1024);
}
fclose($fp);
return $ret;
}
go(function(){
echo getHtml2("t=123");
});
go(function(){
echo getHtml2();
});
// 其他代码段不贴了,当传参t时,sleep一秒后执行
// 目前Runtime函数支持stream_socket_client协程化,所以运行结果 输出先 getHtml2()的内容,后getHtml2("t=123")
// 当然Swoole存在内置函数,可以直接实现协程化,见CoroutineHttpClient
CoroutineMySQL
请勿同时使用异步回调和协程MySQL
使用MySQL协程客户端是否要关闭?1
2
3
4
5
6
7
8
9$swoole_mysql = new SwooleCoroutineMySQL();
$swoole_mysql->connect([
'host' => '127.0.0.1',
'port' => 3306,
'user' => 'user',
'password' => 'pass',
'database' => 'test',
]);
$res = $swoole_mysql->query('select sleep(1)');