1. 程式人生 > >PHP並行程式設計探索之三(定時非同步)

PHP並行程式設計探索之三(定時非同步)

       我們或多或少聽過非同步這個詞,這個詞在前端javascrtipt程式設計中很常見,就是對於定時的或ajax請求任務,我們
不用等待,直接執行接下來的程式碼,直到之前的資料有返回。
我們來看個栗子:

console.log(new Date());
setTimeout(function(){
    console.log(new Date());
    console.log('111')
},5000);
setTimeout(function(){
    console.log(new Date());
    console.log('1114')
},3000);
console.log(new Date());

列印結果:
line:1 Thu Aug 10 2017 20:50:53 GMT+0800 (CST)
line:10 Thu Aug 10 2017 20:50:53 GMT+0800 (CST)
undefined
line:7 Thu Aug 10 2017 20:50:56 GMT+0800 (CST)
line:8 1114
line:3 Thu Aug 10 2017 20:50:58 GMT+0800 (CST)
line:4 111

我們看到第一行和最後一行的語句立馬就打印出來了,但是第8行和第4行的結果則是等了5s才出來
很符合我們的預期,一個定時3s 一個定時5s,我們非同步執行耗時以時間長的為準。


那麼,實際上真是如此麼?我們先來看看PHP中是怎麼實先定時非同步的。

感謝swoole,提供給了我們一個相同的機會實現了與js相同的定時器來實現定時非同步:swoole安裝教程

我們使用下面的程式碼:

function microtime_float()
{
	list($usec, $sec) = explode(" ", microtime());
	return ((float)$usec + (float)$sec);
}
function task1(){  
	echo "wait start" . PHP_EOL;  
	echo "wait end" . PHP_EOL;  
	echo microtime_float()."\n";
};  
  
function task2(){  
	echo "Hello " . PHP_EOL; 
	echo "world!" . PHP_EOL; 
	echo microtime_float()."\n";
}  
echo 'runstart:'.microtime_float()."\n";
swoole_timer_after(3000,'task1');
swoole_timer_after(2000,'task2');
echo 'runend:'.microtime_float()."\n";

列印結果:
runstart:1502370089.7314
runend:1502370089.7315

Hello 
world!
1502370091.735
wait start
wait end
1502370092.7305


按實際的秒數計算確實只用了最長的秒數,所以事實正確了麼,我們單程序的PHP變多程序同時執行多工了麼,
將上面的程式碼稍微改下,我們再看個栗子:

function microtime_float()
{
	list($usec, $sec) = explode(" ", microtime());
	return ((float)$usec + (float)$sec);
}
function task1(){  
	echo "wait start" . PHP_EOL;  
	echo "wait end" . PHP_EOL;  
	sleep(3);//3
	echo microtime_float()."\n";
};  
  
function task2(){  
	echo "Hello " . PHP_EOL; 
	echo "world!" . PHP_EOL; 
	sleep(3);//3
	echo microtime_float()."\n";
}  
echo 'runstart:'.microtime_float()."\n";
swoole_timer_after(3000,'task1');
swoole_timer_after(5000,'task2');
echo 'runend:'.microtime_float()."\n";

執行結果:
runstart:1502377883.6311
runend:1502377883.6312

wait start
wait end
1502377889.6352
Hello 
world!
1502377893.6425

我們發現,實際的執行時間不是8s,而是10s
事實上,計算公式也絕非如此,非同步的真正核心,在於使用者態的排程,實際上始終是一個程序在做事
沒有像多程序那樣,同時有多個程序幫你分擔做事,我們看到的時間重合僅僅是使用者態的定時器時間重合
真正做任務的時候,時間無法重合,因為我們至始至終只有一個程序在幹活。



那麼他的時間其實是:
a. 最小的延時時間    3s 
b. 任務1延時到點了 主執行緒有沒有空 有 3s
c. 任務2到點了  主執行緒有沒有空 沒有 繼續等待 
d. 等到任務1執行完成 主執行緒有空 任務2到點 任務2執行 3s


這裡有點難理解為什麼不是9s,而多出來1s,其實我也有點疑惑,定時器精確到毫秒級別,那麼
程式是不是穩穩準準的在第6s結束執行任務2呢,任務2的定時器在5s的時候沒有得到響應直接給自己加1s延時,
在第6s的時候,發現程式還沒執行完,又給自己加了1s的延時,所以在第7s終於執行了,多了1s,這個解釋可能
稍微合理些,swoole官方的定時器矯正沒有給出相關的說明,但是提到了tick的矯正 


那麼實際上非同步,只是執行阻塞任務時,儲存使用者態,讓非阻塞的任務執行完了,再依次執行阻塞的任務,注意是依次,不是同時。

非同步的優點就是:避開系統排程,重新生成使用者上下文,減少這部分的時間和開銷。

注意:swoole的非同步io程式設計只能在cli 模式下執行。