[原文地址:https://blog.ti-node.com/blog...]php
每每開啓多進程的目的是爲了一塊兒幹活加速效率,前面說了不一樣進程之間的內存空間都是相互隔離的,也就說進程A是沒法讀或寫進程B中的任何數據內容的,反之亦然。可是,有些時候,多個進程之間必需要有相互通知的機制,用職場上的話來講就叫「及時溝通」。你們都在一塊兒作同一件事情的不一樣部分,彼此之間「及時溝通」是很重要的。node
因而進程間通訊就誕生了,英文縮寫IPC,全稱InterProcess Communication。
常見的進程間通訊方式有:管道(分無名和有名兩種)、消息隊列、信號量、共享內存和socket,最後一種方式今天不提,放到後面的php socket編程中去說,重點說前四種方式。linux
管道是*NIX上常見的一個東西,你們平時使用linux的時候也都在用,簡單理解就是|,好比ps -aux|grep php這就是管道,大概意思相似於ps進程和grep進程兩個進程之間用|完成了通訊。管道是一種半雙工(如今也有系統已經支持全雙工的管道)的工做方式,也就是說數據只能沿着管道的一個方向進行傳遞,不能夠在同一個管道上反向傳數據。管道分爲兩種,一種叫作未命名的管道,另外一種叫作命名管道,未命名管道只能在擁有公共祖先的兩個進程之間使用,簡單理解就是隻能用於父進程和和其子進程之間的通訊,可是命名管道則能夠用於任何兩個毫無關連的進程之間的通訊(一下子將要演示的將是這種命名管道)。編程
須要特殊指出的是消息隊列、信號量和共享內存這三種IPC同屬於XSI IPC(XSI能夠認爲是POSIX標準的超集,簡單粗暴理解爲C++之於C)。這三種IPC在*NIX中通常都有兩個「名字」來爲其命名,一個叫作標誌符,一個叫作鍵(key)。標誌符是一個非負整數,每當一個IPC結構被建立而後又被銷燬後,標誌符便會+1一直加到整數的最大整數數值,然後又從0開始從新計算。既然是爲了多進程通訊使用,那麼多進程在使用XSI IPC的時候就須要使用一個名字來找到相應的IPC,而後才能對其進行讀寫(術語叫作多個進程在同一個IPC結構上匯聚),因此POSIX建議是不管什麼時候建立一個IPC結構,都應指定一個鍵(key)與之關聯。一句話總結就是:標誌符是XSI IPC的內部名稱,鍵(key)是XSI IPC的外部名稱。服務器
使多個進程在XSI IPC上匯聚的方法大概有以下三種:網絡
XSI IPC結構有一個與之對應的權限結構,叫作ipc_perm,這個結構中定義了IPC結構的建立者、擁有者等。數據結構
多進程通訊之一:命名管道。 在php中,建立一個管道的函數叫作posix_mkfifo(),管道建立完成後其實就是一個文件,而後就能夠用任何與讀寫文件相關的函數對其進行操做了,代碼大概演示一下:socket
<?php // 管道文件絕對路徑 $pipe_file = __DIR__.DIRECTORY_SEPARATOR.'test.pipe'; // 若是這個文件存在,那麼使用posix_mkfifo()的時候是返回false,不然,成功返回true if( !file_exists( $pipe_file ) ){ if( !posix_mkfifo( $pipe_file, 0666 ) ){ exit( 'create pipe error.'.PHP_EOL ); } } // fork出一個子進程 $pid = pcntl_fork(); if( $pid < 0 ){ exit( 'fork error'.PHP_EOL ); } else if( 0 == $pid ) { // 在子進程中 // 打開命名管道,並寫入一段文本 $file = fopen( $pipe_file, "w" ); fwrite( $file, "helo world." ); exit; } else if( $pid > 0 ) { // 在父進程中 // 打開命名管道,而後讀取文本 $file = fopen( $pipe_file, "r" ); // 注意此處fread會被阻塞 $content = fread( $file, 1024 ); echo $content.PHP_EOL; // 注意此處再次阻塞,等待回收子進程,避免殭屍進程 pcntl_wait( $status ); }
運行結果以下:函數
多進程通訊之二:消息隊列。這個怕是不少人都聽過,不過印象每每停留在kafka、rabbitmq之類的用於服務器解耦網絡消息隊列軟件上。消息隊列是消息的連接表(一種常見的數據結構),可是這種消息隊列存儲於系統內核中(不是用戶態),通常咱們外部程序使用一個key來對消息隊列進行讀寫操做。在PHP中,是經過msg_*系列函數完成消息隊列操做的。ui
<?php // 使用ftok建立一個鍵名,注意這個函數的第二個參數「須要一個字符的字符串」 $key = ftok( __DIR__, 'a' ); // 而後使用msg_get_queue建立一個消息隊列 $queue = msg_get_queue( $key, 0666 ); // 使用msg_stat_queue函數能夠查看這個消息隊列的信息,而使用msg_set_queue函數則能夠修改這些信息 //var_dump( msg_stat_queue( $queue ) ); // fork進程 $pid = pcntl_fork(); if( $pid < 0 ){ exit( 'fork error'.PHP_EOL ); } else if( $pid > 0 ) { // 在父進程中 // 使用msg_receive()函數獲取消息 msg_receive( $queue, 0, $msgtype, 1024, $message ); echo $message.PHP_EOL; // 用完了記得清理刪除消息隊列 msg_remove_queue( $queue ); pcnlt_wait( $status ); } else if( 0 == $pid ) { // 在子進程中 // 向消息隊列中寫入消息 // 使用msg_send()向消息隊列中寫入消息,具體能夠參考文檔內容 msg_send( $queue, 1, "helloword" ); exit; }
運行結果以下:
可是,值得你們繼續深刻研究的是msg_send()和msg_receive()兩個函數,這兩個的每個參數都是很是值得深刻研究和嘗試的。篇幅問題,這裏就再也不詳細描述。
多進程通訊之三:信號量與共享內存。共享內存是最快是進程間通訊方式,由於n個進程之間並不須要數據複製,而是直接操控同一份數據。實際上信號量和共享內存是分不開的,要用也是搭配着用。*NIX的一些書籍中甚至不建議新手輕易使用這種進程間通訊的方式,由於這是一種極易產生死鎖的解決方案。共享內存顧名思義,就是一坨內存中的區域,可讓多個進程進行讀寫。這裏最大的問題就在於數據同步的問題,好比一個在更改數據的時候,另外一個進程不能夠讀,否則就會產生問題。因此爲了解決這個問題才引入了信號量,信號量是一個計數器,是配合共享內存使用的,通常狀況下流程以下:
一個進程再也不使用當前共享資源狀況下,就會將信號量減1。這個地方,信號量的檢測而且減1是原子性的,也就說兩個操做必須一塊兒成功,這是由系統內核來實現的。
在php中,信號量和共享內存前後一共也就這幾個函數:
其中,sem_是信號量相關函數,shm_是共享內存相關函數。
<?php // sem key $sem_key = ftok( __FILE__, 'b' ); $sem_id = sem_get( $sem_key ); // shm key $shm_key = ftok( __FILE__, 'm' ); $shm_id = shm_attach( $shm_key, 1024, 0666 ); const SHM_VAR = 1; $child_pid = []; // fork 2 child process for( $i = 1; $i <= 2; $i++ ){ $pid = pcntl_fork(); if( $pid < 0 ){ exit(); } else if( 0 == $pid ) { // 獲取鎖 sem_acquire( $sem_id ); if( shm_has_var( $shm_id, SHM_VAR ) ){ $counter = shm_get_var( $shm_id, SHM_VAR ); $counter += 1; shm_put_var( $shm_id, SHM_VAR, $counter ); } else { $counter = 1; shm_put_var( $shm_id, SHM_VAR, $counter ); } // 釋放鎖,必定要記得釋放,否則就一直會被阻鎖死 sem_release( $sem_id ); exit; } else if( $pid > 0 ) { $child_pid[] = $pid; } } while( !empty( $child_pid ) ){ foreach( $child_pid as $pid_key => $pid_item ){ pcntl_waitpid( $pid_item, $status, WNOHANG ); unset( $child_pid[ $pid_key ] ); } } // 休眠2秒鐘,2個子進程都執行完畢了 sleep( 2 ); echo '最終結果'.shm_get_var( $shm_id, SHM_VAR ).PHP_EOL; // 記得刪除共享內存數據,刪除共享內存是有順序的,先remove後detach,順序反過來php可能會報錯 shm_remove( $shm_id ); shm_detach( $shm_id );
運行結果以下:
確切說,若是不用sem的話,上述的運行結果在必定機率下就會產生1而不是2。可是隻要加入sem,那就必定保證100%是2,絕對不會出現其餘數值。