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 实现一个连接池,并轻而易举地实现了获取连接的等待功能。
作者:ysp123
链接:https://www.jianshu.com/p/b0f504551748
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。