首先,了解下協(xié)程是什么??
協(xié)程是一種用戶態(tài)的輕量級(jí)線程,協(xié)程的調(diào)度完全由用戶控制。協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時(shí),將寄存器上下文和棧保存到其他地方,在切回來(lái)的時(shí)候,恢復(fù)先前保存的寄存器上下文和棧,直接操作棧則基本沒(méi)有內(nèi)核切換的開(kāi)銷,可以不加鎖的訪問(wèn)全局變量,所以上下文的切換非常快。
swoole創(chuàng)建協(xié)程
go(function(){
echo "coroutine 111";
});
echo "main hello";
go(function(){
echo "coroutine 222";
});

更改下體驗(yàn)協(xié)程調(diào)度
go(function(){
Co::sleep(2);
echo "coroutine 111";
});
echo "main hello";
go(function(){
echo "coroutine 222";
});

執(zhí)行過(guò)程:當(dāng)執(zhí)行g(shù)o的時(shí)候生成一個(gè)協(xié)程,當(dāng)協(xié)程遇到阻塞(Co::sleep(2)),協(xié)程讓出控制,進(jìn)入?yún)f(xié)程控制隊(duì)列。繼續(xù)往下執(zhí)行。輸出coroutine 222,等待之前的的協(xié)程阻塞解除,輸出coroutine 111
以上只是簡(jiǎn)單的協(xié)程認(rèn)識(shí),接下來(lái)我們看下協(xié)程到底快在那里?
$start_time = time();
for($i=0;$i<500;$i++){
$url = "http://www.baidu.com/";
$content = file_get_contents($url);
echo "普通{$i}已完成".PHP_EOL;
}
echo "非協(xié)程完成時(shí)間:".(time() - $start_time).PHP_EOL;
結(jié)果如圖
$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 "協(xié)程{$i} 完成,耗時(shí)".(time()-$start_time).PHP_EOL;
});
}
結(jié)果如圖:
管道(channel)
Channel 管道:支持多生產(chǎn)者協(xié)程和多消費(fèi)者協(xié)程。底層自動(dòng)實(shí)現(xiàn)了協(xié)程的切換和調(diào)度。
Channel 管道是用于同一進(jìn)程內(nèi)協(xié)程之間交換數(shù)據(jù)的工具,可以理解為,Channel 就是一個(gè)實(shí)現(xiàn)了協(xié)程切換和調(diào)度的隊(duì)列,亦或是數(shù)組。
生產(chǎn)協(xié)程:在channel已滿時(shí),會(huì)被掛起;
消費(fèi)協(xié)程:在channel為空是,也會(huì)被掛起。
$chan = new Chan();
go(function()use($chan){
for($i=0;$i<5;$i++){
$chan->push($i);
echo "順序插入{$i}".PHP_EOL;
}
});
echo "順序執(zhí)行".PHP_EOL;
go(function()use($chan){
while(!$chan->isEmpty()){
$res = $chan->pop();
echo "順序消費(fèi){$res}".PHP_EOL;
}
});

結(jié)果可以看出生產(chǎn)者協(xié)程和消費(fèi)者協(xié)程是交替運(yùn)行的,而協(xié)程切換的時(shí)機(jī)則是在運(yùn)行到 push 和 pop 的時(shí)候,首先會(huì)進(jìn)入生產(chǎn)者協(xié)程,然后生產(chǎn)了一條數(shù)據(jù),然后代碼繼續(xù)執(zhí)行輸出“順序執(zhí)行”的字符串并創(chuàng)建了消費(fèi)者協(xié)程;由于前面已經(jīng) push 了一條數(shù)據(jù)所以此時(shí)的 $channel->isEmpty() 是非空狀態(tài),再執(zhí)行 pop。
鏈接池
由于管道(channel)的特性(寫入消費(fèi)),可以通過(guò)管道實(shí)現(xiàn)連接池。
連接池是一個(gè)用于分配和管理連接的容器,可以避免在高并發(fā)的系統(tǒng)下反復(fù)地去創(chuàng)建和銷毀連接,便于連接的復(fù)用。
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();
}
/** 創(chuàng)建數(shù)據(jù)庫(kù)連接 */
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();
});
以上就是一個(gè)簡(jiǎn)單的鏈接池,通過(guò) Channel 實(shí)現(xiàn)一個(gè)連接池,并輕而易舉地實(shí)現(xiàn)了獲取連接的等待功能。