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上會不同霸饲。
子進程會復(fù)制父進程中所有的數(shù)據(jù)、代碼臂拓、狀態(tài)等信息厚脉。當使用pcntl_fork
成功創(chuàng)建子進程后,子進程會復(fù)制父進程的代碼和數(shù)據(jù)埃儿。此時父進程和子進程擁有相同的代碼和數(shù)據(jù)器仗。子進程也會復(fù)制父進程的狀態(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)資源尿赚。
多線程存在的問題主要有
- 線程讀寫變量存在著同步問題需要加鎖
- 鎖粒度過大會存在性能問題散庶,會導(dǎo)致只有一個線程在運行,其它線程都在等待鎖凌净,也就無法實現(xiàn)并行悲龟。
- 同時使用多個鎖時邏輯復(fù)雜,一旦某個鎖沒有被正確釋放可能會發(fā)生線程死鎖冰寻。
- 某個線程發(fā)生致命錯誤會導(dǎo)致整個進程崩潰
相對而言多進程更為穩(wěn)定须教,可利用進程間通信IPC
技術(shù)實現(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服務(wù)器,主進程accept
客戶端連接蜘犁。當有新的連接到來時直接fork
一個子進程翰苫,子進程中循環(huán)recv/send
處理數(shù)據(jù)。這種模式在請求量不多的情況下很實用这橙,例如FTP服務(wù)器奏窑。
在過去多數(shù)Linux程序都時采用這種模式,簡單高效屈扎,代碼量少埃唯。當有幾百個并發(fā)的情況下表現(xiàn)不錯,但在大并發(fā)的情況下消耗就會過大。
例如:每個子進程都能創(chuàng)建一個與之對應(yī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);
}
}