目錄
何時不使用 pthreads
處理一次性任務
回收線程
pthreads 和(非)可變性
同步
結論
關於在 PHP 中使用 Pthreads 進行並行編程的常見問題解答 (FAQ)
使用 Pthreads 在 PHP 中的先決條件是什麼?
如何在 PHP 中安裝 Pthreads?
如何使用 Pthreads 在 PHP 中創建一個新線程?
如何使用 Pthreads 在 PHP 中在線程之間共享數據?
如何處理 Pthreads 中的錯誤?
我可以在 Laravel 或 Symfony 等 PHP 框架中使用 Pthreads 嗎?
如何調試使用 Pthreads 的 PHP 腳本?
我可以在 Web 服務器環境中使用 Pthreads 嗎?
如何使用 Pthreads 在 PHP 中停止正在運行的線程?
是否有用於 PHP 中並行編程的 Pthreads 的替代方案?
首頁 後端開發 php教程 與PHP中的Pthreads平行編程 - 基本面

與PHP中的Pthreads平行編程 - 基本面

Feb 10, 2025 am 08:57 AM

Parallel Programming with Pthreads in PHP - the Fundamentals

關鍵要點

  • 避免在Web 服務器環境中使用pthreads: 由於安全性和可擴展性問題,不應在FCGI 等Web 服務器環境中使用pthreads,因為它在這些環境中無法有效處理多個線程。
  • 將pthreads 用於一次性任務或IO 綁定操作: 對於執行一次或需要大量IO 操作的任務,使用pthreads 可以幫助卸載主執行線程,並通過在單獨的線程中處理這些操作來提高性能。
  • 回收線程以優化資源: 為每個任務創建新線程可能會佔用大量資源;相反,請通過 Worker 或 Pool 類重用線程,以便更有效地管理和執行多個任務。
  • 了解pthreads 的不變性和Volatile 類: 默認情況下,擴展Threaded 的對象的屬性是不可變的,以避免性能下降,Volatile 類提供了一種在必要時管理可變屬性的方法。
  • 實現同步以確保線程安全: 為防止數據損壞並確保多個線程訪問共享資源時的一致結果,請使用pthreads 提供的同步方法,例如同步塊和Threaded::wait 和Threaded::notify 等方法。

本文由 Christopher Pitt 審核。感謝所有 SitePoint 的同行評審者,使 SitePoint 內容盡善盡美!


PHP 開發人員似乎很少利用並行性。同步、單線程編程的簡單性確實很有吸引力,但有時使用一點並發可以帶來一些值得的性能改進。

在本文中,我們將了解如何使用 pthreads 擴展在 PHP 中實現線程。這需要安裝 ZTS(Zend 線程安全)版本的 PHP 7.x,以及安裝 pthreads v3。 (在撰寫本文時,PHP 7.1 用戶需要從 pthreads repo 的主分支安裝——請參閱本文的部分內容,了解有關從源代碼構建第三方擴展的詳細信息。)

快速說明一下:pthreads v2 面向 PHP 5.x,不再受支持;pthreads v3 面向 PHP 7.x,並且正在積極開發中。

Parallel Programming with Pthreads in PHP - the Fundamentals

非常感謝 Joe Watkins(pthreads 擴展的創建者)校對並幫助改進我的文章!

何時不使用 pthreads

在我們繼續之前,我想首先說明您不應該(以及不能)使用 pthreads 擴展的情況。

在 pthreads v2 中,建議不要在 Web 服務器環境(即在 FCGI 進程中)中使用 pthreads。從 pthreads v3 開始,此建議已強制執行,因此您現在根本不能在 Web 服務器環境中使用它。這樣做的兩個主要原因是:

  1. 在這種環境中使用多個線程是不安全的(會導致 IO 問題以及其他問題)。
  2. 它不能很好地擴展。例如,假設您有一個 PHP 腳本,該腳本創建一個新線程來處理一些工作,並且該腳本在每次請求時都會執行。這意味著對於每個請求,您的應用程序都會創建一個新線程(這是一個 1:1 線程模型——一個線程對應一個請求)。如果您的應用程序每秒處理 1,000 個請求,那麼它每秒就會創建 1,000 個線程!在單個機器上運行如此多的線程很快就會使它不堪重負,並且隨著請求速率的增加,這個問題只會加劇。

這就是為什麼線程在這種環境中不是一個好解決方案的原因。如果您正在尋找線程作為 IO 阻塞任務(例如執行 HTTP 請求)的解決方案,那麼讓我向您指出異步編程的方向,這可以通過 Amp 等框架實現。 SitePoint 發布了一些關於此主題的優秀文章(例如編寫異步庫和使用 PHP 修改 Minecraft),如果您感興趣的話。

言歸正傳,讓我們直接進入正題!

處理一次性任務

有時,您希望以多線程方式處理一次性任務(例如執行一些 IO 綁定任務)。在這種情況下,可以使用 Thread 類創建一個新線程,並在該單獨線程中運行一些工作單元。

例如:

$task = new class extends Thread {
    private $response;

    public function run()
    {
        $content = file_get_contents("http://google.com");
        preg_match("~<title>(.+)</title>~", $content, $matches);
        $this->response = $matches[1];
    }
};

$task->start() && $task->join();

var_dump($task->response); // string(6) "Google"
登入後複製
登入後複製
登入後複製

在上面,run 方法是我們將在新線程中執行的工作單元。調用 Thread::start 時,將生成新線程並調用 run 方法。然後,我們將生成的線程重新加入到主線程(通過 Thread::join),這將阻塞,直到單獨的線程完成執行。這確保了在嘗試輸出結果(存儲在 $task->response 中)之前,任務已完成執行。

將線程相關的邏輯(包括必須定義 run 方法)污染類的職責可能並不理想。我們可以通過讓它們擴展 Threaded 類來隔離這些類,然後可以在其他線程中運行它們:

class Task extends Threaded
{
    public $response;

    public function someWork()
    {
        $content = file_get_contents('http://google.com');
        preg_match('~<title>(.+)</title>~', $content, $matches);
        $this->response = $matches[1];
    }
}

$task = new Task;

$thread = new class($task) extends Thread {
    private $task;

    public function __construct(Threaded $task)
    {
        $this->task = $task;
    }

    public function run()
    {
        $this->task->someWork();
    }
};

$thread->start() && $thread->join();

var_dump($task->response);
登入後複製
登入後複製
登入後複製

任何需要在單獨線程中運行的類都必須以某種方式擴展 Threaded 類。這是因為它提供了在不同線程中運行的必要能力,以及提供隱式安全性和有用的接口(用於資源同步等)。

讓我們快速了解一下 pthreads 公開的類層次結構:

<code>Threaded (implements Traversable, Collectable)
    Thread
        Worker
    Volatile
Pool</code>
登入後複製
登入後複製

我們已經了解了 Thread 和 Threaded 類的基礎知識,所以現在讓我們來看看其餘三個(Worker、Volatile 和 Pool)。

回收線程

為每個要並行化的任務啟動一個新線程是昂貴的。這是因為為了在 PHP 內部實現線程,pthreads 必須採用共享無狀態架構。這意味著必須為創建的每個線程複製 PHP 解釋器當前實例的整個執行上下文(包括每個類、接口、特性和函數)。由於這會造成明顯的性能影響,因此應始終盡可能重用線程。可以通過兩種方式重用線程:使用 Worker 或使用 Pool。

Worker 類用於在另一個線程中同步執行一系列任務。這是通過創建一個新的 Worker 實例(這將創建一個新線程),然後將任務堆疊到該單獨線程(通過 Worker::stack)來完成的。

這是一個簡單的示例:

$task = new class extends Thread {
    private $response;

    public function run()
    {
        $content = file_get_contents("http://google.com");
        preg_match("~<title>(.+)</title>~", $content, $matches);
        $this->response = $matches[1];
    }
};

$task->start() && $task->join();

var_dump($task->response); // string(6) "Google"
登入後複製
登入後複製
登入後複製

輸出:

Parallel Programming with Pthreads in PHP - the Fundamentals

上面通過 Worker::stack 將 15 個任務堆疊到新的 $worker 對像上,然後按堆疊順序處理它們。如上所示,Worker::collect 方法用於在任務完成執行後清理任務。通過在 while 循環中使用它,我們阻塞主線程,直到所有堆疊的任務都完成執行並已清理完畢,然後我們觸發 Worker::shutdown。過早關閉工作程序(即在仍有待執行的任務時)仍將阻塞主線程,直到所有任務都完成執行——任務只是不會被垃圾回收(導致內存洩漏)。

Worker 類提供了一些其他與任務堆棧相關的 method,包括 Worker::unstack 用於刪除最舊的堆疊項,以及 Worker::getStacked 用於執行堆棧上的項目數量。工作程序的堆棧只保存要執行的任務。一旦堆棧中的任務執行完畢,它就會被刪除,然後放在另一個(內部)堆棧上以進行垃圾回收(使用 Worker::collect)。

在執行許多任務時重用線程的另一種方法是使用線程池(通過 Pool 類)。線程池由一組 Worker 驅動,以使任務能夠並發執行,其中並發因子(池運行的線程數)在池創建時指定。

讓我們調整上面的示例以使用工作程序池:

class Task extends Threaded
{
    public $response;

    public function someWork()
    {
        $content = file_get_contents('http://google.com');
        preg_match('~<title>(.+)</title>~', $content, $matches);
        $this->response = $matches[1];
    }
}

$task = new Task;

$thread = new class($task) extends Thread {
    private $task;

    public function __construct(Threaded $task)
    {
        $this->task = $task;
    }

    public function run()
    {
        $this->task->someWork();
    }
};

$thread->start() && $thread->join();

var_dump($task->response);
登入後複製
登入後複製
登入後複製

輸出:

Parallel Programming with Pthreads in PHP - the Fundamentals

使用池與使用工作程序之間存在一些顯著差異。首先,池不需要手動啟動,它們會在任務可用時立即開始執行任務。其次,我們將任務提交到池中,而不是堆疊它們。此外,Pool 類不擴展 Threaded,因此它可能不會傳遞到其他線程(與 Worker 不同)。

作為良好實踐,應始終在完成任務後收集工作程序和池的任務,並手動關閉它們。通過 Thread 類創建的線程也應重新加入創建者線程。

pthreads 和(非)可變性

最後一個要介紹的類是 Volatile——pthreads v3 的一個新補充。不變性已成為 pthreads 中的一個重要概念,因為如果沒有它,性能會嚴重下降。因此,默認情況下,本身是 Threaded 對象的 Threaded 類的屬性現在是不可變的,因此在初始賦值後不能重新賦值。現在更傾向於對這些屬性進行顯式可變性,並且仍然可以通過使用新的 Volatile 類來完成。

讓我們快速查看一個示例來演示新的不變性約束:

$task = new class extends Thread {
    private $response;

    public function run()
    {
        $content = file_get_contents("http://google.com");
        preg_match("~<title>(.+)</title>~", $content, $matches);
        $this->response = $matches[1];
    }
};

$task->start() && $task->join();

var_dump($task->response); // string(6) "Google"
登入後複製
登入後複製
登入後複製

另一方面,Volatile 類的 Threaded 屬性是可變的:

class Task extends Threaded
{
    public $response;

    public function someWork()
    {
        $content = file_get_contents('http://google.com');
        preg_match('~<title>(.+)</title>~', $content, $matches);
        $this->response = $matches[1];
    }
}

$task = new Task;

$thread = new class($task) extends Thread {
    private $task;

    public function __construct(Threaded $task)
    {
        $this->task = $task;
    }

    public function run()
    {
        $this->task->someWork();
    }
};

$thread->start() && $thread->join();

var_dump($task->response);
登入後複製
登入後複製
登入後複製

我們可以看到,Volatile 類覆蓋了其父類 Threaded 類強制執行的不變性,以允許重新分配(以及取消設置)Threaded 屬性。

關於可變性和 Volatile 類,還有一個最後一個基本主題需要介紹——數組。當將數組分配給 Threaded 類的屬性時,pthreads 中的數組會自動強制轉換為 Volatile 對象。這是因為在 PHP 中從多個上下文中操作數組根本不安全。

讓我們再次快速查看一個示例以更好地理解:

<code>Threaded (implements Traversable, Collectable)
    Thread
        Worker
    Volatile
Pool</code>
登入後複製
登入後複製

我們可以看到,Volatile 對象可以像對待數組一樣對待,因為它們為基於數組的操作(如上所示)提供了對子集運算符([])的支持。但是,Volatile 類不受常見的基於數組的函數(例如 array_pop 和 array_shift)的支持。相反,Threaded 類為我們提供了這些操作作為內置方法。

作為演示:

class Task extends Threaded
{
    private $value;

    public function __construct(int $i)
    {
        $this->value = $i;
    }

    public function run()
    {
        usleep(250000);
        echo "Task: {$this->value}\n";
    }
}

$worker = new Worker();
$worker->start();

for ($i = 0; $i < 15; $i++) {
    $worker->stack(new Task($i));
}

while ($worker->collect());

$worker->shutdown();
登入後複製

其他受支持的操作包括 Threaded::chunk 和 Threaded::merge。

同步

本文將介紹的最後一個主題是 pthreads 中的同步。同步是一種允許控制訪問共享資源的技術。

例如,讓我們實現一個簡單的計數器:

class Task extends Threaded
{
    private $value;

    public function __construct(int $i)
    {
        $this->value = $i;
    }

    public function run()
    {
        usleep(250000);
        echo "Task: {$this->value}\n";
    }
}

$pool = new Pool(4);

for ($i = 0; $i < 15; $i++) {
    $pool->submit(new Task($i));
}

while ($pool->collect());

$pool->shutdown();
登入後複製

如果不使用同步,則輸出不是確定性的。多個線程寫入單個變量而不進行受控訪問會導致更新丟失。

讓我們通過添加同步來糾正這個問題,以便我們獲得正確的輸出 20:

class Task extends Threaded // a Threaded class
{
    public function __construct()
    {
        $this->data = new Threaded();
        // $this->data is not overwritable, since it is a Threaded property of a Threaded class
    }
}

$task = new class(new Task()) extends Thread { // a Threaded class, since Thread extends Threaded
    public function __construct($tm)
    {
        $this->threadedMember = $tm;
        var_dump($this->threadedMember->data); // object(Threaded)#3 (0) {}
        $this->threadedMember = new StdClass(); // invalid, since the property is a Threaded member of a Threaded class
    }
};
登入後複製

同步代碼塊還可以使用 Threaded::wait 和 Threaded::notify(以及 Threaded::notifyOne)相互協作。

以下是來自兩個同步 while 循環的交錯增量:

class Task extends Volatile
{
    public function __construct()
    {
        $this->data = new Threaded();
        $this->data = new StdClass(); // valid, since we are in a volatile class
    }
}

$task = new class(new Task()) extends Thread {
    public function __construct($vm)
    {
        $this->volatileMember = $vm;

        var_dump($this->volatileMember->data); // object(stdClass)#4 (0) {}

        // still invalid, since Volatile extends Threaded, so the property is still a Threaded member of a Threaded class
        $this->volatileMember = new StdClass();
    }
};
登入後複製

您可能已經註意到,在對 Threaded::wait 的調用周圍添加了其他條件。這些條件至關重要,因為它們只允許同步回調在收到通知並且指定條件為真時恢復。這很重要,因為通知可能來自 Threaded::notify 的調用以外的地方。因此,如果對 Threaded::wait 的調用未包含在條件中,我們將容易受到虛假喚醒調用的影響,這將導致代碼不可預測。

結論

我們已經看到了 pthreads 附帶的五個類(Threaded、Thread、Worker、Volatile 和 Pool),包括介紹每個類的用法。我們還研究了 pthreads 中新的不變性概念,以及對其支持的同步功能的快速瀏覽。涵蓋了這些基礎知識後,我們現在可以開始研究將 pthreads 應用於一些實際用例!這將是我們下一篇文章的主題。

與此同時,如果您對 pthreads 有任何應用程序創意,請隨時在下面的評論區中留下您的想法!

關於在 PHP 中使用 Pthreads 進行並行編程的常見問題解答 (FAQ)

使用 Pthreads 在 PHP 中的先決條件是什麼?

要在 PHP 中使用 Pthreads,您需要具備 PHP 和麵向對象編程的工作知識。您還需要安裝啟用 ZTS(Zend 線程安全)的 PHP。 Pthreads 在標準 PHP 安裝中不可用;它需要使用線程安全構建的 PHP 版本。您可以通過在終端中運行命令“php -i | grep “Thread Safety””來檢查您的 PHP 安裝是否啟用了 ZTS。如果它返回“Thread Safety => enabled”,那麼您可以使用 Pthreads。

如何在 PHP 中安裝 Pthreads?

要安裝 Pthreads,您需要使用 PECL,即 PHP 擴展社區庫。首先,確保您已安裝啟用 ZTS 的 PHP。然後,在您的終端中,運行命令“pecl install pthreads”。如果安裝成功,您需要將行“extension=pthreads.so”添加到您的 php.ini 文件中。這將在每次運行 PHP 時加載 Pthreads 擴展。

如何使用 Pthreads 在 PHP 中創建一個新線程?

要創建一個新線程,您需要定義一個擴展 Pthreads 提供的 Thread 類的類。在此類中,您將覆蓋 run() 方法,這是在新線程中將執行的代碼。然後,您可以創建此類的實例並調用其 start() 方法來啟動新線程。

如何使用 Pthreads 在 PHP 中在線程之間共享數據?

Pthreads 提供 Threaded 類用於在線程之間共享數據。您可以創建一個此類的新的實例並將其傳遞給您的線程。在此對像上設置的任何屬性都將在線程之間安全共享。

如何處理 Pthreads 中的錯誤?

Pthreads 中的錯誤處理類似於標準 PHP 中的錯誤處理。您可以使用 try-catch 塊來捕獲異常。但是,請注意,每個線程都有其自己的範圍,因此一個線程中的異常不會影響其他線程。

我可以在 Laravel 或 Symfony 等 PHP 框架中使用 Pthreads 嗎?

Pthreads 與 Laravel 或 Symfony 等 PHP 框架不兼容。這是因為這些框架並非設計為線程安全的。如果您需要在這些框架中執行並行處理,請考慮使用其他技術,例如隊列或異步任務。

如何調試使用 Pthreads 的 PHP 腳本?

調試使用 Pthreads 的 PHP 腳本可能具有挑戰性,因為每個線程都在其自己的上下文中運行。但是,您可以使用標準調試技術,例如記錄或將數據輸出到控制台。您還可以使用像 Xdebug 這樣的 PHP 調試器,但請注意,並非所有調試器都支持多線程應用程序。

我可以在 Web 服務器環境中使用 Pthreads 嗎?

不建議在 Web 服務器環境中使用 Pthreads。它專為 CLI(命令行界面)腳本設計。在 Web 服務器環境中使用 Pthreads 會導致不可預測的結果,並且通常是不安全的。

如何使用 Pthreads 在 PHP 中停止正在運行的線程?

要停止正在運行的線程,您可以使用 Pthreads 提供的 kill() 方法。但是,應謹慎使用此方法,因為如果線程處於操作過程中,它可能會導致不可預測的結果。通常最好設計您的線程,以便它們可以乾淨地完成其任務。

是否有用於 PHP 中並行編程的 Pthreads 的替代方案?

是的,有幾種用於 PHP 中並行編程的 Pthreads 的替代方案。這些包括 forks,這是一個提供用於創建和管理子進程的接口的 PECL 擴展;以及 parallel,這是 PHP 7.2 中引入的原生 PHP 擴展,它提供了一個更簡單、更安全的並行編程接口。

以上是與PHP中的Pthreads平行編程 - 基本面的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

會話如何劫持工作,如何在PHP中減輕它? 會話如何劫持工作,如何在PHP中減輕它? Apr 06, 2025 am 12:02 AM

會話劫持可以通過以下步驟實現:1.獲取會話ID,2.使用會話ID,3.保持會話活躍。在PHP中防範會話劫持的方法包括:1.使用session_regenerate_id()函數重新生成會話ID,2.通過數據庫存儲會話數據,3.確保所有會話數據通過HTTPS傳輸。

描述紮實的原則及其如何應用於PHP的開發。 描述紮實的原則及其如何應用於PHP的開發。 Apr 03, 2025 am 12:04 AM

SOLID原則在PHP開發中的應用包括:1.單一職責原則(SRP):每個類只負責一個功能。 2.開閉原則(OCP):通過擴展而非修改實現變化。 3.里氏替換原則(LSP):子類可替換基類而不影響程序正確性。 4.接口隔離原則(ISP):使用細粒度接口避免依賴不使用的方法。 5.依賴倒置原則(DIP):高低層次模塊都依賴於抽象,通過依賴注入實現。

在PHPStorm中如何進行CLI模式的調試? 在PHPStorm中如何進行CLI模式的調試? Apr 01, 2025 pm 02:57 PM

在PHPStorm中如何進行CLI模式的調試?在使用PHPStorm進行開發時,有時我們需要在命令行界面(CLI)模式下調試PHP�...

框架安全功能:防止漏洞。 框架安全功能:防止漏洞。 Mar 28, 2025 pm 05:11 PM

文章討論了框架中的基本安全功能,以防止漏洞,包括輸入驗證,身份驗證和常規更新。

如何在系統重啟後自動設置unixsocket的權限? 如何在系統重啟後自動設置unixsocket的權限? Mar 31, 2025 pm 11:54 PM

如何在系統重啟後自動設置unixsocket的權限每次系統重啟後,我們都需要執行以下命令來修改unixsocket的權限:sudo...

PHP 8.1中的枚舉(枚舉)是什麼? PHP 8.1中的枚舉(枚舉)是什麼? Apr 03, 2025 am 12:05 AM

PHP8.1中的枚舉功能通過定義命名常量增強了代碼的清晰度和類型安全性。 1)枚舉可以是整數、字符串或對象,提高了代碼可讀性和類型安全性。 2)枚舉基於類,支持面向對象特性,如遍歷和反射。 3)枚舉可用於比較和賦值,確保類型安全。 4)枚舉支持添加方法,實現複雜邏輯。 5)嚴格類型檢查和錯誤處理可避免常見錯誤。 6)枚舉減少魔法值,提升可維護性,但需注意性能優化。

See all articles