2015-09-20 58 views
1

我在使用php pthreads for Windows從全局上下文訪問資源時遇到問題。pthreads在PHP中訪問全局資源

的邏輯非常簡單:有一個服務器作爲主類,只有一個,這將:

  • 創建日誌文件
  • 創建幾個線程
  • 每個線程都需要寫的東西此日誌文件

問題是,日誌文件句柄的資源在某種程度上完全從線程內部搞砸了。當我創建資源時,沒關係,我可以寫入它。當我嘗試從正在運行的線程中調用日誌時,日誌文件處理程序的資源看起來是整數0,甚至不是資源。

這裏是我的代碼:

$main_server = new CMainServer(); 
$main_server->init(); 
$main_server->go(); 

$main_server->log("All done"); 

裏面的CMainServer類:

class CMainServer 
{ 
    private $logfile = null; 

    public function init() 
    { 
    $this->logfile = fopen('wstext.log', 'w'); 
    } 

    public function log($str) 
    { 
    if ($this->logfile === null) 
    { 
     echo "[".date("H:i:s", time())."]: logfile is null<BR />"; 
     return false; 
    } 

    if (!is_resource($this->logfile)) 
    { 
     echo "[".date("H:i:s", time())."]: logfile is NOT a resource, can't write {$str}<BR />"; 
     return false; 
    } 

    echo "[".date("H:i:s", time())."]: logfile is resource, not null, writing {$str}<BR />"; 
    flush(); 

    fwrite($this->logfile, "[".date("H:i:s", time())."]: {$str}\r\n"); 
    return true; 
    } 

    public function go() 
    { 
    $this->log('Before creating a thread'); 

    $first_thread = new CThread(); 
    $first_thread->start(PTHREADS_INHERIT_ALL | PTHREADS_ALLOW_GLOBALS); 
    $second_thread = new CThread(); 
    $second_thread->start(PTHREADS_INHERIT_ALL | PTHREADS_ALLOW_GLOBALS); 
    $first_thread->join(); 
    $second_thread->join(); 
    } 

    public function __destruct() 
    { 
    if ($this->logfile) 
     fclose($this->logfile); 
    } 
} 

最後,在CThread類:

class CThread extends Thread 
{ 
    public function run() 
    { 
    global $main_server; 
    $thread_id = $this->getThreadId(); 

    Thread::globally(function() 
    { 
     for ($i = 0; $i < 2; $i++) 
     { 
     $main_server->log("({$i}) writing random number ".rand(0, 100)." to log from running thread id={$thread_id}"); 
     sleep(1); 
     } 
    }); 
    } 
} 

結果是可悲的:

[13:38:10]: logfile is NOT a resource, can't write (0) writing random number 21 to log from running thread id=9080 
[13:38:11]: logfile is NOT a resource, can't write (1) writing random number 91 to log from running thread id=9080 
[13:38:10]: logfile is NOT a resource, can't write (0) writing random number 16 to log from running thread id=17316 
[13:38:11]: logfile is NOT a resource, can't write (1) writing random number 50 to log from running thread id=17316 
[13:38:10]: logfile is resource, not null, writing Before creating a thread 
[13:38:12]: logfile is resource, not null, writing All done 

所以,雖然我在線外,一切都很好。但是,從一個線程內,$ logfile根本就不是資源。

我嘗試了不同的選擇:從CThread打過電話:: run()的全局函數:

function LogFromThread($i, $thread_id) 
{ 
    global $main_server; 

    $main_server->log("({$i}) writing random number ".rand(0, 100)." to log from running thread id={$thread_id}"); 
} 

的結果是一樣的。

沒有線程::全局()的嘗試,但都沒有好處。

我正在運行Apache/2.4.10 (Win32) OpenSSL/1.0.1i PHP/5.6.3,試過pthreads版本2.0.8,2.0.9。也嘗試過用PHP 7RC2和RC3,但似乎有一個問題開始一個新的線程,apache記錄一個錯誤,所以我回到了5.6.3。

也許有人可以給我一個這方面的提示嗎?

非常感謝! =)

回答

3

不要嘗試在線程中使用全局變量。

PTHREADS_ALLOW_GLOBALS不斷和功能是有一個特殊的用例,它不是爲所有人使用,此外globally已被刪除在v3。

有做你想做的事的更爲整潔的方式,實際工作。

資源是官方支持,這並不意味着你不能使用它們,就意味着你不應該期望能夠上下文之間共享。

在這種情況下,你並不需要共享資源,因此應該不嘗試,也不應該儘量做到在全球範圍內任何東西。

下面是一些PHP7代碼(我建議的新項目應並行線程V3採用的是大大優於V2):

<?php 
class Logger extends Threaded { 

    public function __construct(string $file) { 
     $this->file = $file; 
    } 

    private function getHandle() { 
     if (!self::$handle) { 
      self::$handle = fopen($this->file, "a"); 
     } 

     return self::$handle; 
    } 

    public function log(string $message, ... $args) { 
     return $this->synchronized(function() use($message, $args) { 
      return vfprintf($this->getHandle(), $message, $args); 
     }); 
    } 

    private $file; 
    private static $handle; 
} 

class My extends Thread { 

    public function __construct(Logger $logger) { 
     $this->logger = $logger; 
    } 

    public function run() { 
     while (@$i++<100) { 
      $this->logger->log("Hello %s from %s #%lu\n", 
       "World", __CLASS__, $this->getThreadId()); 

      /* just simulating work, don't need to wait here */ 
      $this->synchronized(function(){ 
       $this->wait(1000); 
      }); 
     } 
    } 

    private $logger; 
} 

$logger = new Logger("/tmp/log.txt"); 
$threads = []; 
while (@$i++ < 10) { 
    $threads[$i] = new My($logger); 
    $threads[$i]->start(); 
} 

foreach ($threads as $thread) 
    $thread->join(); 
?> 

Logger,你會注意到,手柄進行靜態存儲,這對於並行線程表示線程局部。

這意味着,每個線程都有一個句柄數,這是資源打算如何在PHP中使用。

您還會注意到,log方法被包裝在一個同步塊中,原因是:如果許多線程試圖同時寫入日誌,那麼將會有一個滿是亂碼的日誌。

同步提供互斥,以便一次只有一個線程可以寫入日誌,我創建了10個線程並讓它們全部寫入日誌一百次,沒有亂碼出現,它的行爲就像到處都是。

有關文件鎖定(羣)一點點:在某些操作系統上,在某些情況下,追加是原子。值得一提的,因爲你永遠不應該依賴於這個,也不應該試圖迫使寫是原子與羊羣,因爲羊羣只能放在一個文件諮詢鎖:使用正確的權限執行的過程是免費的忽略無論如何,你都可以成羣結隊地操縱文件。

可怕的東西,不依靠追加爲原子,並且不依賴羊羣在多線程的上下文中,唯一明智的建議。

請注意,如果您認爲您在pthreads(v3,PHP7)中發現了一個錯誤,請在github上報告。