溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

PHP PCNTL多進(jìn)程編程

發(fā)布時(shí)間:2020-06-01 19:56:52 來(lái)源:網(wǎng)絡(luò) 閱讀:1581 作者:yorkershi 欄目:web開(kāi)發(fā)

PHP中的PCNTL可以實(shí)現(xiàn)多進(jìn)程編程,由于項(xiàng)目場(chǎng)景需要,試用了一下,感觸頗多,也長(zhǎng)了不少見(jiàn)識(shí),就此對(duì)遇到的問(wèn)題小做一個(gè)總結(jié),以備不時(shí)之需。


問(wèn)題一:fork泛濫

我想在一個(gè)父進(jìn)程中起10個(gè)子程來(lái)完成我的工作,代碼如下:

for($i = 0; $i < 10; $i++) {
    $pid = pcntl_fork();
    if($pid  == -1) {
        echo "Could not fork!\n";
        exit(1);
    }
    if(!$pid) {
        //child process workspace
        //TODO
        Api::refreshCache();        
    }
}

由于Api::refreshCache邏輯中有數(shù)據(jù)庫(kù)操作,代碼一執(zhí)行,就把DB搞死了,報(bào)了N多個(gè)too many connections。我以為是DB原本就抽風(fēng)了,檢查DB,正常。

最終發(fā)現(xiàn),這一段代碼fork的可不是10個(gè)子進(jìn)程,而是

20 + 21 + 22+ 23 + 24 + ... + 29 = 210-1 = 1023 個(gè)子進(jìn)程。

恐怖了吧?這是因?yàn)樽舆M(jìn)程又fork子進(jìn)程,并且子進(jìn)程不共享父進(jìn)程$i變量更新的值,由此導(dǎo)致數(shù)量成指數(shù)關(guān)系增長(zhǎng)。

為了避免這個(gè)問(wèn)題,代碼修改如下:

for($i = 0; $i < 10; $i++) {
    $pid = pcntl_fork();
    if($pid  == -1) {
        echo "Could not fork!\n";
        exit(1);
    }
    if(!$pid) {
        //child process workspace
        //TODO
        Api::refreshCache(); 
        exit; //子進(jìn)程邏輯執(zhí)行完后,馬上退出,以免往下走再fork子進(jìn)程,不好控制     
    }
}

只要在子進(jìn)程邏輯執(zhí)行完后,加一個(gè)exit,一切都在撐握中了。這樣,只有一個(gè)父進(jìn)程,父進(jìn)程后續(xù)對(duì)子進(jìn)程的管理也會(huì)清晰很多。


問(wèn)題二:?jiǎn)卫J较翫B連接被虐

/*
 * 前面或遠(yuǎn)或近的地方,已經(jīng)有一個(gè)$db = &MySql::getInstance();了
 */
for($i = 0; $i < 10; $i++) {
    $pid = pcntl_fork();
    if($pid  == -1) {
        echo "Could not fork!\n";
        exit(1);
    }
    if(!$pid) {
        //child process workspace
        //TODO
        $db = &MySql::getInstance();
        $sql = "XXX";
        $result = $db->getAll($sql);
        exit;
    }
}

這段代碼,十有八九都會(huì)報(bào)mysql has gone away.或者其它一些數(shù)據(jù)fetch方面的錯(cuò)誤。究其原因,是因?yàn)楦鱾€(gè)子進(jìn)程創(chuàng)建時(shí),就已經(jīng)繼承了父進(jìn)程一份完全一樣的拷貝。對(duì)象可以拷貝,但是已創(chuàng)建的連接可不能拷貝成多個(gè),由此產(chǎn)生的結(jié)果,就是各個(gè)進(jìn)程都使用同一個(gè)mysql連接,各干各的事,最終產(chǎn)生莫名其妙的沖突。

解決辦法:

我們顯然不可能完全保證在fork進(jìn)程之前,父進(jìn)程不會(huì)創(chuàng)建mysql連接實(shí)例,因此,解決方案只能靠子進(jìn)程本身了??梢韵胂?,我們只需要在子進(jìn)程中獲取的實(shí)例只與當(dāng)前進(jìn)程相關(guān),這個(gè)問(wèn)題就不存在了。解決辦法就是稍微改造一下mysql類(lèi)實(shí)例化的靜態(tài)方式,與當(dāng)前進(jìn)程ID綁定。

public static function &getInstance() {
    static $instances = array();
    $key = getmypid(); //獲取當(dāng)前進(jìn)程ID
    if(empty($instances[$key])) {
        //實(shí)例化mysql類(lèi)動(dòng)作
        $classname = __CLASS__;
        $instances[$key] = new $classname();
    }
    return $instances[$key];
}


總結(jié):子進(jìn)程創(chuàng)建時(shí),拷貝了父進(jìn)程當(dāng)時(shí)擁有的所有資源,雖說(shuō)后面對(duì)資源的修改互不影響,但DB連接卻還是同一個(gè),造成運(yùn)行混亂。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI