pthreads在PHP中全局访问资源

时间:2015-09-20 11:49:11

标签: php multithreading pthreads

我在使用php pthreads for Windows从全局上下文访问资源时遇到问题。

逻辑非常简单:服务器作为主类,只有一个,它将:

  • 创建日志文件
  • 创建几个主题
  • 每个线程都需要写一些东西给这个日志文件

问题是,日志文件句柄的资源在某种程度上完全从线程内部搞砸了。当我创建资源时,它很好,我可以写入它。当我尝试从正在运行的线程内部调用日志时,logfile处理程序的资源似乎是整数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}");
}

结果是一样的。

尝试没有Thread :: global(),但一切都没有。

我正在运行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。

也许有人可以给我一个暗示吗?

非常感谢! =)

1 个答案:

答案 0 :(得分:3)

不要尝试在线程中使用全局变量。

PTHREADS_ALLOW_GLOBALS常量和功能适用于特殊用例,并非适合所有人使用,此外在v3中已删除globally

有一种更为整洁的方式来做你想做的事情,实际上是有效的。

资源是官方不受支持的,并不意味着您无法使用它们,这意味着您不应期望能够在上下文之间共享它们。

在这种情况下,您不需要共享资源,所以不应该尝试,也不应该尝试在全球范围内做任何事情。

以下是一些PHP7代码(我推荐新项目应该使用,因为pthreads 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中你会注意到句柄是静态存储的,对于pthreads来说意味着线程本地存储。

这意味着每个线程都有一个日志句柄,这就是在PHP中使用资源的方式。

你还会注意到,log方法被包装在一个synchronized块中,原因是:如果许多线程同时尝试写一个日志,你将会有一个充满乱码的日志。 / p>

同步提供互斥,这样一次只有一个线程可以写日志,我创建十个线程并让它们全部写入日志一百次,没有乱码,它会像所有地方一样。< / p>

关于文件锁定(flock):在某些操作系统上,在某些情况下,append是原子的。值得一提的是,因为你不应该依赖这个,也不应该试图强制写入与flock是原子的,因为flock只会在文件上放置建议锁:执行的进程无论如何,正确的权限都可以忽略 flocks并操纵文件。

可怕的东西,不要依赖于附加原子,并且不依赖于flock是多线程背景下唯一明智的建议。

注意,如果您认为在pthreads(v3,PHP7)中发现了一个错误,请在github上报告。