前些天幫同事查一個問題,第一次接觸到了 PHP 的多線程,原覺得 PHP 廣泛都是單線程模型,並不適合多線程領域,花些時間翻了幾個多線程的項目源碼以後,發現 PHP 的多線程也很有可取之處,活用起來,用來解決某些問題居然很是適合。php
因而找了幾篇文章看了下 PHP 多線程 TSRM
機制的實現,也有所收穫,詳情能夠查看下面的參考文章。本文對比多進程介紹了下多線程的優點和適用場景,提出了一種巧用方案,並使用 PHP 代碼實現了多線程的常見用法。html
文章歡迎轉載,但請註明來源:http://www.cnblogs.com/zhenbianshu/p/7978835.html, 謝謝。程序員
首先說下線程:數據庫
線程(thread) 是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務.編程
使用多線程主要是由於它在執行效率上有很大優點。因爲線程是操做系統可以進行調度的最小單位
:數組
同時對比多進程程序,多線程有如下特色:安全
多線程的優化是不少,但是無腦使用多線程並不能提高程序的執行效率,由於線程的建立和銷燬、上下文切換、線程同步等也是有性能損耗的,耗費時間可能比順序執行的代碼還多。如:網絡
sumSmall
是一個從1累加到50000的函數。多線程
上圖是在主線程內執行了三次 sumSmall 和三個線程分別執行 sumSmall ,再將結果同步到一個線程的時間對比,咱們會發現只在主線程執行的時間反而更短,三個線程建立、切換、同步的時間遠遠大過了線程異步執行節省的時間。併發
而函數 sumLarge 從1累加到5000000,下圖同一線程執行三次和三個線程執行的耗時:
此次,多線程終於有效率優點了。
是否使用多線程還須要根據具體需求而定,通常考慮如下兩種狀況:
PHP 默認並不支持多線程,要使用多線程須要安裝 pthread 擴展,而要安裝 pthread 擴展,必須使用 --enable-maintainer-zts
參數從新編譯 PHP,這個參數是指定編譯 PHP 時使用線程安全方式。
多線程是讓程序變得不安分的一個因素,在使用多線程以前,首先要考慮線程安全問題:
線程安全:線程安全是編程中的術語,指某個函數、函數庫在多線程環境中被調用時,可以正確地處理多個線程之間的共享變量,使程序功能正確完成。
在傳統多線程中,因爲多個線程共享變量,因此可能會致使出現以下問題:
$arr = array('a');
;$a = array_pop($arr); $a = 'a';
;$b = array_pop($arr); $a = null;
;PHP 實現的線程安全主要是使用 TSRM
機制對 全局變量和靜態變量進行了隔離
,將全局變量和靜態變量 給每一個線程都複製了一份,各線程使用的都是主線程的一個備份,從而避免了變量衝突,也就不會出現線程安全問題。
PHP 對多線程的封裝保證了線程安全,程序員不用考慮對全局變量加各類鎖來避免讀寫衝突了,同時也減小了出錯的機會,寫出的代碼更加安全。
但由此致使的是,子線程一旦開始運行,主線程便沒法再對子線程運行細節進行調整了,線程必定程度上失去了線程之間經過全局變量進行消息傳遞的能力。
同時 PHP 開啓線程安全選項後,使用 TSRM
機制分配和使用變量時也會有額外的損耗,因此在不須要多線程的 PHP 環境中,使用 PHP 的 ZTS (非線程安全) 版本就好。
PHP 將線程 封裝成了 Thread
類,線程的建立經過實例化一個線程對象來實現,因爲類的封裝性,變量的使用只能經過構造函數傳入,而線程運算結果也須要經過類變量傳出。
下面介紹幾個經常使用的 Thread 類方法:
run()
:此方法是一個抽象方法,每一個線程都要實現此方法,線程開始運行後,此方法中的代碼會自動執行;start()
:在主線程內調用此方法以開始運行一個線程;join()
:各個線程相對於主線程都是異步執行,調用此方法會等待線程執行結束;kill()
:強制線程結束;isRunning()
:返回線程的運行狀態,線程正在執行run()
方法的代碼時會返回 true;由於線程安全的實現,PHP 的多線程開始運行後,沒法再經過共享內存空間通訊,線程也沒法經過線程間通訊複用,因此我認爲 PHP 的「線程池」並無什麼意義。擴展內自帶的Pool
類是一個對多線程分配管理的類,這裏也再也不多介紹了。
下面是一個線程類,用來請求某一接口。接下來根據它寫兩個多線程的應用實例:
class Request extends Thread { public $url; public $response; public function __construct($url) { $this->url = $url; } public function run() { $this->response = file_get_contents($this->url); } }
將同步的請求拆分爲多個線程異步調用,以提高程序的運行效率。
$chG = new Request("www.google.com"); $chB = new Request("www.baidu.com"); $chG ->start(); $chB ->start(); $chG->join(); $chB->join(); $gl = $chG->response; $bd = $chB->response;
偶然間發現公司網站某一網頁上的一塊內容時有時無,不知道具體實現,但這給了我使用多線程的靈感:利用線程異步實現快速失敗和超時控制。
咱們在使用 curl 請求某個地址時,能夠經過 CURLOPT_CONNECTTIMEOUT / CURLOPT_TIMEOUT
參數分別設置 curl 的鏈接超時時間和讀取數據超時時間,但總的超時時間很差控制。並且在進行數據庫查詢時的超時時間沒法設置(鳥哥博客:爲MySQL設置查詢超時)。
這時咱們即可以借用多線程來實現此功能:在執行線程類的 start()
方法後,不調用 join()
方法,使線程一直處於異步狀態,不阻塞主線程的執行。
此時主線程至關於旗艦,而各子線程至關於巡航艦,旗艦到達某地後沒必要要一直等待巡航艦也歸來,等待一段時間後離開便可,從而避免巡航艦意外時旗艦白白空等。
代碼:
$chG = new Request("www.google.com"); $chB = new Request("www.baidu.com"); $chG->start(); $chB->start(); $chB->join(); // 此處不對chG執行join方法 sleep(1); // sleep一個能接受的超時時間 $gl = $chG->response; $bd = $chB->response; $bd->kill(); if (!$gl) { $gl = ""; // 處理異常,或在線程類內給$gl一個默認值 }
PHP 對多線程進行的封(yan)裝(ge),讓人用線程用得很是不盡興。雖然安全,也保持 PHP 簡單易用的一向風格,卻沒法徹底發揮多線程的能力。不過各個語言各有特點和側重點,也沒必要強求,愛她就要包容她 =_=。
最近在重學操做系統和 Linux 內核方面的知識,對程序的認知有了很大提高,感受很是有必要總結一下,敬請期待。