swoole 协程(Coroutine)和通道(Channel)
首先,了解下协程是什么??
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
swoole创建协程
go(function(){
echo "coroutine 111";
});
echo "main hello";
go(function(){
echo "coroutine 222";
});

更改下体验协程调度
go(function(){
Co::sleep(2);
echo "coroutine 111";
});
echo "main hello";
go(function(){
echo "coroutine 222";
});

执行过程:当执行go的时候生成一个协程,当协程遇到阻塞(Co::sleep(2)),协程让出控制,进入协程控制队列。继续往下执行。输出coroutine 222,等待之前的的协程阻塞解除,输出coroutine 111
以上只是简单的协程认识,接下来我们看下协程到底快在那里?
$start_time = time();
for($i=0;$i<500;$i++){
$url = "http://www.baidu.com/";
$content = file_get_contents($url);
echo "普通{$i}已完成".PHP_EOL;
}
echo "非协程完成时间:".(time() - $start_time).PHP_EOL;
结果如图

$start_time = time();
for($i=0;$i<500;$i++){
go(function()use($i, $start_time){
$cli = new Swoole\Coroutine\Http\Client('http://www.baidu.com');
$cli->setHeaders([
'Host' => "www.baidu.com",
"User-Agent" => 'Chrome/49.0.2587.3',
'Accept' => 'text/html,application/xhtml+xml,application/xml',
'Accept-Encoding' => 'gzip',
]);
$cli->get('/');
$cli->close();
echo "协程{$i} 完成,耗时".(time()-$start_time).PHP_EOL;
});
}
结果如图:

管道(channel)
Channel 管道:支持多生产者协程和多消费者协程。底层自动实现了协程的切换和调度。
Channel 管道是用于同一进程内协程之间交换数据的工具,可以理解为,Channel 就是一个实现了协程切换和调度的队列,亦或是数组。
生产协程:在channel已满时,会被挂起;
消费协程:在channel为空是,也会被挂起。
$chan = new Chan();
go(function()use($chan){
for($i=0;$i<5;$i++){
$chan->push($i);
echo "顺序插入{$i}".PHP_EOL;
}
});
echo "顺序执行".PHP_EOL;
go(function()use($chan){
while(!$chan->isEmpty()){
$res = $chan->pop();
echo "顺序消费{$res}".PHP_EOL;
}
});

结果可以看出生产者协程和消费者协程是交替运行的,而协程切换的时机则是在运行到 push 和 pop 的时候,首先会进入生产者协程,然后生产了一条数据,然后代码继续执行输出“顺序执行”的字符串并创建了消费者协程;由于前面已经 push 了一条数据所以此时的 $channel->isEmpty() 是非空状态,再执行 pop。
链接池
由于管道(channel)的特性(写入消费),可以通过管道实现连接池。
连接池是一个用于分配和管理连接的容器,可以避免在高并发的系统下反复地去创建和销毁连接,便于连接的复用。
class db{
public $pool;
public $config = [
'maxnum'=>20,
'mysql' =>[
'host'=>'127.0.0.1',
'port'=>3306,
'user'=>'root',
'password'=>'',
'database'=>'demo'
]
];
public function __construct(){
$maxnum = $this->config['maxnum'];
$this->pool = new \Swoole\Coroutine\Channel($maxnum);
for($i=1;$i<$maxnum;$i++){
$mysqlConnect = $this->createConn();
$this->push($mysqlConnect);
}
}
public function push($source){
return $this->pool->push($source);
}
public function get(){
return $this->pool->pop();
}
public function length(){
return $this->pool->length();
}
/** 创建数据库连接 */
public function createConn(){
$mysql = new \Swoole\Coroutine\MySQL();
$mysql->connect($this->config['mysql']);
return $mysql;
//$res = $mysql->query('select 1+1 as sum');
//var_dump($res);
}
}
go(function(){
$obj = new db();
$conn = $obj->get();
$res = $conn->query('select 1+1 as sum');
var_dump($res);
//echo $obj->length();
//$obj->createConn();
});
以上就是一个简单的链接池,通过 Channel 实现一个连接池,并轻而易举地实现了获取连接的等待功能。