探索 PHP 多程式

luler發表於2020-01-15

鑑於上兩篇文章,有些同學對php的多程式表現出很大的興趣,所以我又要來獻醜了,感覺不錯就點贊,感覺有幫助就打賞,反正要鼓勵一下寶寶,哈哈哈。

程式是系統進行資源分配和排程的基本單位,執行一個php程式,預設只會生成一個程式,當該程式發生請求IO時,就會被掛起,讓出CPU,php的多程式程式設計的目的就是要在多個程式併發的執行任務,當其中一些程式由於發生IO被掛起時,還有一些程式可以利用CPU執行任務,這樣就可以充分利用系統資源來完成我們的大批次任務了(特別適合IO型任務)。php多程式開發,主要有兩條路:

  1. 第一就是php內建的pcntl擴充套件(依賴linux系統),
  2. 第二就是比較流行的擴充套件swoole下的多程式。

以下我們來實際操作一波!

1. pcntl_fork

不多說了,直接上程式碼:

        $pid = pcntl_fork(); //fork一個子程式
        if ($pid > 0) { //父程式會獲取到fork到的子程式的程式id
            dump('我是父程式');
        } elseif ($pid == 0) { //$pid=0時為剛剛派生出的子程式
            dump('我是子程式');
        } else {
            dump('建立子程式失敗');
        }

執行結果如下,證明建立成功:

探索 PHP 多程式

就那麼簡單嗎?不是的。還有很多關鍵程式建立和使用過程要注意的點,以下再來深入探索一下:

  1. 孤兒程式

孤兒程式已經很白話了,建立它的父程式死掉了,但是它自己還沒死,還在執行,但是linux憲法規定,凡是父程式死了還在執行的子程式都要認程式號為1的程式做父親,這樣的話,子程式就是可以脫離ssh,直接程式後臺執行模式,由乾爹程式1來管理了。利用這一點,可以開發守護程式。

        $pid = pcntl_fork();
        if ($pid > 0) {
            $seconds = 5;
            while ($seconds--) {
                dump('我是父程式,我的壽命只有5秒,剩餘:' . $seconds);
                sleep(1);
            }
            dump('我是父程式');
        } elseif ($pid == 0) {
            $seconds = 10;
            while ($seconds--) {
                dump('我是子程式,我的壽命有10秒,剩餘::' . $seconds);
                sleep(1);
            }
        } else {
            dump('建立子程式失敗');
        }

執行結果如下

探索 PHP 多程式

前5秒,父子程式協同執行,保證正常的父子關係

systemd,1 --switched-root --system --deserialize 22
  ├─sshd,1090 -D -u0
  │   ├─sshd,3711
  │   │   └─sshd,3713
  │   │       └─bash,3714
  │   │           └─sudo,3741 -i
  │   │               └─bash,3742
  │   │                   └─php,11178 think debug  //父程式
  │   │                       └─php,11179 think debug  //子程式

後5秒,父程式死掉了,子程式退出ssh,認了程式號為1的程式做乾爹,繼續執行5秒,執行完畢被程式1收回,如果一直執行就成為守護程式了。

systemd,1 --switched-root --system --deserialize 22
  ├─php,11179 think debug
  1. 殭屍程式

殭屍程式就是已經執行完了,exit了,但是父程式沒有主動回收資源,父子程式關係沒有變化,所以依然存在於程式表中。由於殭屍程式會佔用系統資源,所以是有危害的,應該避免。怎麼模擬產生呢,很簡單,將上面的程式碼變一下,使父程式長命於子程式,如下:

        $pid = pcntl_fork();
        if ($pid > 0) {
            $seconds = 10;
            while ($seconds--) {
                dump('我是父程式,我的壽命只有10秒,剩餘:' . $seconds);
                sleep(1);
            }
            dump('我是父程式');
        } elseif ($pid == 0) {
            $seconds = 5;
            while ($seconds--) {
                dump('我是子程式,我的壽命有5秒,剩餘::' . $seconds);
                sleep(1);
            }
        } else {
            dump('建立子程式失敗');
        }
    }

執行結果如下:

探索 PHP 多程式

前5秒,父子程式協同執行,保證正常的父子關係

systemd,1 --switched-root --system --deserialize 22
  ├─sshd,1090 -D -u0
  │   ├─sshd,3711
  │   │   └─sshd,3713
  │   │       └─bash,3714
  │   │           └─sudo,3741 -i
  │   │               └─bash,3742
  │   │                   └─php,11204 think debug
  │   │                       └─php,11205 think debug

後5秒,子程式死掉了,父進正常執行,但是子程式還存在於程式表中,佔用系統資源,直到父程式也死掉才會一起被釋放。

  ├─sshd,1090 -D -u0
  │   ├─sshd,3711
  │   │   └─sshd,3713
  │   │       └─bash,3714
  │   │           └─sudo,3741 -i
  │   │               └─bash,3742
  │   │                   └─php,11204 think debug
  │   │                       └─(php,11205)  //殭屍程式
  1. 避免殭屍程式和孤兒程式

想要避免出現殭屍程式和孤兒程式,主要是保證兩點:一是,父程式不能早死於子程式;二是,父程式能及時回收執行結束的子程式。這方面,主要使用php提供了兩個函式來解決:pcntl_wait、pcntl_waitpid。編輯程式碼如下:

        $pid = pcntl_fork();
        if ($pid) {
            dump('這裡是父程式');
            pcntl_wait($status); //主程式會掛起,等待第一個子程式結束
            //等同於, pcntl_waitpid($pid,$status);
            sleep(10); //模擬幹活
        } elseif ($pid == 0) {
            sleep(20); //模擬幹活
            dump('這裡是子程式');
        } else {
            dump('建立子程式異常');
        }

執行上面程式碼,發現殭屍程式和孤兒程式問題解決了,因為主程式始終保持等待和回收子程式,但是這樣之後,任務執行變成了序列的、同步的,根本實現不了父子程式同時幹活的目的。

  1. 多程式非同步非阻塞工作

毫不猶豫,懟程式碼:

        $pids = [];
        for ($i = 0; $i < 20; $i++) {
            $pid = pcntl_fork();
            if ($pid) {
                $pids[] = $pid;
            } elseif ($pid == 0) {
                sleep(20);
                dump('這裡是子程式');
                exit();//執行完,一定要結束,不然就會走進建立子程式的死迴圈
            } else {
                dump('建立子程式異常');
            }
        }
        //等待回收所有子程式
        foreach ($pids as $pid) {
            pcntl_waitpid($pid, $status);
            //第三個引數有兩個可能值:WNOHANG-回收子程式時非同步不掛起主程式
            //                      WUNTRACED-子程式已經退出並且其狀態未報告時返回
        }

執行結果如下,整個過程20秒結束

  ├─sshd,1073 -D -u0
  │   ├─sshd,3133
  │   │   └─sshd,3135
  │   │       └─bash,3136
  │   │           └─sudo,3163 -i
  │   │               └─bash,3164
  │   │                   └─php,3361 think debug
  │   │                       ├─php,3363 think debug
  │   │                       ├─php,3364 think debug
  │   │                       ├─php,3365 think debug
  │   │                       ├─php,3366 think debug
  │   │                       ├─php,3367 think debug
  │   │                       ├─php,3368 think debug
  │   │                       ├─php,3369 think debug
  │   │                       ├─php,3370 think debug
  │   │                       ├─php,3371 think debug
  │   │                       ├─php,3372 think debug
  │   │                       ├─php,3373 think debug
  │   │                       ├─php,3374 think debug
  │   │                       ├─php,3375 think debug
  │   │                       ├─php,3376 think debug
  │   │                       ├─php,3377 think debug
  │   │                       ├─php,3378 think debug
  │   │                       ├─php,3379 think debug
  │   │                       ├─php,3380 think debug
  │   │                       ├─php,3381 think debug
  │   │                       └─php,3382 think debug

這裡的程式碼設計,主程式負責建立、回收子程式,子程式負責實際的幹活,整個過程都是非同步的,所以執行效率非常高。

  1. 程式間通訊-共享記憶體

php程式間的共享記憶體函式有兩大類,簡介如下:

I:首先簡單介紹一下shmop_xxx類

相關函式,使用起來也簡單,不做一一解釋了(偷懶)

探索 PHP 多程式

編輯程式碼如下

        $key = ftok(__FILE__, 'c'); //ipc標識
        $shmid = shmop_open($key, 'c', 0655, 1024);//申請1024位元組的記憶體空間
        $pid = pcntl_fork();
        if ($pid > 0) {
            sleep(2);//睡眠一下,等待子程式寫資料進記憶體
            $info = shmop_read($shmid, 0, 100);//讀取記憶體資料
            dump('我是父程式,從共享程式中讀取到的資訊是:' . $info);
            pcntl_wait($status); //回收子程式
        } elseif ($pid == 0) {
            //從申請的共享記憶體的第0個位元組開始寫資料
            $size = shmop_write($shmid, "hello father,i'm your son!", 0);
            dump('我是子程式,我向記憶體裡寫入位元組數是:' . $size);
            exit();
        } else {
            dump('建立子程式失敗');
        }

執行結果如下,父子程式能夠透過透過共享記憶體實現資訊交流

探索 PHP 多程式

II:再來介紹一下shm_xxx類,需要安裝Semaphore 擴充套件

相關函式,使用起來也簡單,不做一一解釋了(再次偷懶)

探索 PHP 多程式

編輯程式碼如下:

       $key = ftok(__FILE__, 'c'); //ipc標識
        $shmid = shm_attach($key, 1024, 0655);
        $variable_key = 1; //類似設定key-value的key,不過這裡要求是int
        $pid = pcntl_fork();
        if ($pid > 0) {
            sleep(2);//睡眠一下,等待子程式寫資料進記憶體
            $info = shm_get_var($shmid, $variable_key);//獲取對應key的值
            dump('我是父程式,從共享程式中讀取到的資訊是:' . $info);
            pcntl_wait($status); //回收子程式
        } elseif ($pid == 0) {
            //對key設定值
            $size = shm_put_var($shmid, $variable_key, "hello father,i'm your son!");
            dump('我是子程式,我向記憶體裡寫入位元組數是:' . $size);
            exit();
        } else {
            dump('建立子程式失敗');
        }

        shm_remove($shmid); // 從系統中移除
        shm_detach($shmid); //關閉和共享記憶體的連線

III:除了上面的方式之外,還可以使用Redis、memcache等方式實現程式間的資料共享

  1. 程式間通訊-訊號量

訊號量相關函式如下,如需細究,請自行研究:

探索 PHP 多程式

以下我懟一下自己粗狂的程式碼:

        $key = ftok(__FILE__, 'c'); //ipc標識
        $signal_id = sem_get($key); //獲取訊號id
        $pids = [];
        for ($i = 0; $i < 5; $i++) {
            $pid = pcntl_fork();
            if ($pid > 0) {
                $pids[] = $pid;
                sem_acquire($signal_id); //獲取訊號量(阻塞式)
                $datetime = date('Y-m-d H:i:s');
                dump("我是父程式,正在建立程式:{$i},建立時間:{$datetime}");
                sem_release($signal_id);//釋放訊號量
            } elseif ($pid == 0) {
                sem_acquire($signal_id); //獲取訊號量(阻塞式)
                sleep(2);
                sem_release($signal_id);//釋放訊號量
                exit();
            } else {
                dump('建立子程式失敗');
            }
            sem_remove($signal_id);//刪除訊號id
            //等待回收所有子程式
            foreach ($pids as $pid) {
                  pcntl_waitpid($pid, $status);
            }
        }

執行結果如下:

探索 PHP 多程式

可以發現,父程式不是一下子生成5個子程式的,因為子程式做了訊號量控制,當某個程式獲得訊號量時,其他程式處於等待訊號量時間,只有擁有訊號量的程式釋放訊號量時,其他程式才能獲得,所以上面父程式每次建立子程式後,要等子程式睡兩秒,然後再繼續建立下去,如此類推。

2. swoole多程式

swoole的官網學習文件,https://wiki.swoole.com/wiki/page/p-proces...

程式是十分昂貴的資源,php每個程式佔用記憶體從幾M到幾百M不等,過多的建立程式反而會嚴重拖垮系統,使系統變得緩慢,甚至當機。php程式都是獨立的執行單位,程式間的資料庫、Redis連線都是獨立的,要做好資源使用控制和程式間通訊。php多程式程式設計適用於cli模式,對於php-fpm執行模式下,儘量不要採取多程式程式設計,會出現無法預測的問題。





原創不易,分享快樂,渴望動力

淺談併發加鎖

本作品採用《CC 協議》,轉載必須註明作者和本文連結
我只想看看藍天

相關文章