我在使用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。
也许有人可以给我一个暗示吗?
非常感谢! =)
答案 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上报告。