pcntl是一個可以利用操作系統(tǒng)的fork系統(tǒng)調(diào)用在PHP中實現(xiàn)多線程的進程控制擴展,當使用fork系統(tǒng)調(diào)用后執(zhí)行的代碼將會是并行的。pcntl僅適用于Linux平臺的CLI模式下使用。
PHP官方?jīng)]有提供多線程的擴展,在pecl中有一個pthread擴展提供了多線程的特性,此版本僅在線程安全版本中可用。
創(chuàng)建子進程pcntl_fork
int pcntl_fork(void)
當pcntl_fork函數(shù)執(zhí)行時會在當前進程下創(chuàng)建一個子進程,子進程與父進程在PID和PPID上會不同。
子進程會復制父進程中所有的數(shù)據(jù)、代碼、狀態(tài)等信息。當使用pcntl_fork成功創(chuàng)建子進程后,子進程會復制父進程的代碼和數(shù)據(jù)。此時父進程和子進程擁有相同的代碼和數(shù)據(jù)。子進程也會復制父進程的狀態(tài)。
當使用pcntl_fork創(chuàng)建子進程,如果成功則會在父進程中將會返回0,在子進程中會返回自身的進程編號PID。如果創(chuàng)建失敗則返回-1。
使用pcntl_fork創(chuàng)建的進程只是一個分支節(jié)點,相當于一個標記,父進程完成后子進程會從標記處繼續(xù)執(zhí)行,也就是說在pcntl_fork之后的代碼分別會被父進程和子進程執(zhí)行兩遍,而兩個進程在執(zhí)行過程中得到的返回值卻是不同的,因此才可以分離父子進程執(zhí)行不同的代碼。
在Linux環(huán)境下可使用ps命令查看進程
<?php
$pid = pcntl_fork();
if($pid > 0){
//父進程
exit(0);
}elseif($pid == 0){
//子進程
exit(0);
}
多進程和多線程的作用相同,區(qū)別主要在于
- 多個線程是在同一個進程內(nèi)的,線程之間可以共享內(nèi)存變量而實現(xiàn)線程間的通信。
- 線程比進程更加輕量級,進程要比線程更加消耗系統(tǒng)資源。
多線程存在的問題主要有
- 線程讀寫變量存在著同步問題需要加鎖
- 鎖粒度過大會存在性能問題,會導致只有一個線程在運行,其它線程都在等待鎖,也就無法實現(xiàn)并行。
- 同時使用多個鎖時邏輯復雜,一旦某個鎖沒有被正確釋放可能會發(fā)生線程死鎖。
- 某個線程發(fā)生致命錯誤會導致整個進程崩潰
相對而言多進程更為穩(wěn)定,可利用進程間通信IPC技術實現(xiàn)數(shù)據(jù)共享。多進程通信的方式主要包括
- 共享內(nèi)存
共享內(nèi)存和線程間讀寫變量時一樣的,都需要加鎖,同時也存在同步、死鎖等問題。 - 消息隊列
消息隊列采用多個子進程搶占隊列的模式,性能較好。 - 管道、UnixSock、TCP、UDP
可以使用read/write來傳遞數(shù)據(jù),TCP/UDP使用socket來通信,子進程可以分布運行。
利用fork系統(tǒng)調(diào)用可以實現(xiàn)并發(fā)的TCP服務器,主進程accept客戶端連接。當有新的連接到來時直接fork一個子進程,子進程中循環(huán)recv/send處理數(shù)據(jù)。這種模式在請求量不多的情況下很實用,例如FTP服務器。
在過去多數(shù)Linux程序都時采用這種模式,簡單高效,代碼量少。當有幾百個并發(fā)的情況下表現(xiàn)不錯,但在大并發(fā)的情況下消耗就會過大。
例如:每個子進程都能創(chuàng)建一個與之對應的文件,父進程也創(chuàng)建一個屬于自己的文件。
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, 0);
if($socket < 0){
$errmsg = socket_strerror($socket);
echo "failed to create socket: {$errmsg}".PHP_EOL;
exit;
}
$host = "0.0.0.0";
$port = 9601;
$ret = socket_bind($socket, $host, $port);
if($ret < 0){
echo "failed to bind socket: {$ret}".PHP_EOL;
exit;
}
$ret = socket_listen($socket, 0);
if($ret < 0){
$errmsg = socket_strerror($ret);
echo "failed to listen: {$errmsg}".PHP_EOL;
exit;
}
while(pcntl_fork() == 0){
$connection = @socket_accept($socket);
if(pcntl_fork() == 0){
$recv = socket_read($connection ,8192);
$data = "serverr: {$recv}";
socket_write($connection ,$data);
socket_close($connection);
exit(0);
}else{
socket_close($connection);
}
}