最近使用扩展程序pthreads,我发现了一个异常现象。我有一个内部状态的简单对象:
class Sum {
private $value = 0;
public function add($inc) { $this->value += $inc; }
public function getValue() { return $this->value; }
}
现在我创建了一个Thread类,它对这个对象做了一些事情:
class MyThread extends Thread {
private $sum;
public function __construct(Sum $sum) {
$this->sum = $sum;
}
public function run(){
for ($i=0; $i < 10; $i++) {
$this->sum->add(5);
echo $this->sum->getValue() . " ";
}
}
}
在我的main函数中,我创建了一个Sum对象,将其注入到线程中并启动它:
$sum = new Sum();
$thread = new MyThread($sum);
$thread->start();
$thread->join();
echo $sum->getValue();
我希望结果为50
,因为线程必须将值增加10倍。但是我得到了0
!
更奇怪的是,它不是同步回主线程失败但线程甚至似乎忘记了它的内部状态:run()
方法内的回声输出是不是预期的5 10 15 20 25 30 35 40 45 50
,而是0 0 0 0 0 0 0 0 0 0
。没有人干涉线程 - 为什么它不保留其状态?
附注:如果我没有启动线程而是直接在主线程($thread->run();
)中调用run() - 方法,结果仍然相同。但是,如果我现在删除类声明中的extends Thread
,它将完美地运行并返回预期的5 10 15 20 25 30 35 40 45 50
。
答案 0 :(得分:17)
任何不是来自pthreads定义的对象都会在设置为来自pthreads的对象的成员时被序列化。
+ =和[]等操作在内部使用指针,序列化与其他对象的指针不兼容。在介绍页面的手册中,它声明任何打算由多个上下文操作的对象应该扩展Stackable,Thread或Worker,如
<?php
class Sum extends Stackable {
private $value = 0;
public function add($inc) { $this->value += $inc; }
public function getValue() { return $this->value; }
public function run(){}
}
class MyThread extends Thread {
public $sum;
public function __construct(Sum $sum) {
$this->sum = $sum;
}
public function run(){
for ($i=0; $i < 10; $i++) {
$this->sum->add(5);
echo $this->sum->getValue() . " ";
}
}
}
$sum = new Sum();
$thread = new MyThread($sum);
$thread->start();
$thread->join();
echo $sum->getValue();
?>
如果Sum没有使用指针,你可以选择在加入后从线程对象中检索引用。
这些操作很简单,不需要同步。您应该同步的唯一时间是您计划等待对象或通知对象。
与pthreads捆绑在一起的对象非常适合这种环境,从不进行序列化。
请阅读手册中的介绍以及您希望使用的方法中的所有示例,以确切了解其内容,然后随时问为什么:)
我知道PHP的用户不习惯进行研究,但是我们正在推动这方面的工作,你会发现有正确的方法以不正确的方式做事,其中大部分都记录在示例中,并且任何我不确定的东西都会从SO中提取出来并最终找到它的文档。
我不确定您提供的示例是否特别测试了对象,但您提供的代码不必是两个对象,也不应该是两个对象,请考虑以下内容:
<?php
class MyThread extends Thread {
public $sum;
public function run(){
for ($i=0; $i < 10; $i++) {
$this->add(5);
printf("%d ", $this->sum);
}
}
public function add($num) { $this->sum += $num; }
public function getValue() { return $this->sum; }
}
$thread = new MyThread();
$thread->start();
$thread->join();
var_dump($thread->getValue());
?>
您可以通过解释看到更多功能,这里有一个与您类似的示例:
<?php
class MyThread extends Thread {
public $sum;
public function __construct() {
$this->sum = 0;
}
public function run(){
for ($i=0; $i < 10; $i++) {
$this->add(5);
$this->writeOut("[%d]: %d\n", $i, $this->sum);
}
$this->synchronized(function($thread){
$thread->writeOut("Sending notification to Process from %s #%lu ...\n", __CLASS__, $thread->getThreadId());
$thread->notify();
}, $this);
}
public function add($num) { $this->sum += $num; }
public function getValue() { return $this->sum; }
/* when two threads attempt to write standard output the output will be jumbled */
/* this is a good use of protecting a method so that
only one context can write stdout and you can make sense of the output */
protected function writeOut($format, $args = null) {
$args = func_get_args();
if ($args) {
vprintf(array_shift($args), $args);
}
}
}
$thread = new MyThread();
$thread->start();
/* so this is synchronization, rather than joining, which requires an actual join of the underlying thread */
/* you can wait for notification that the thread is done what you started it to do */
/* in these simple tests the time difference may not be apparent, but in real complex objects from */
/* contexts populated with more than 1 object having executed many instructions the difference may become very real */
$thread->synchronized(function($thread){
if ($thread->getValue()!=50) {
$thread->writeOut("Waiting for thread ...\n");
/* you should only ever wait _for_ something */
$thread->wait();
$thread->writeOut("Process recieved notification from Thread ...\n");
}
}, $thread);
var_dump($thread->getValue());
?>
这结合了一些简单示例中的一些更高级的功能,并进行了评论以帮助您。关于共享对象的主题,如果Thread对象包含其他线程或stackables中所需的某些功能和数据,则传递Thread对象没有任何问题。您应该尽可能少地使用线程和对象来完成工作。
答案 1 :(得分:1)
您的问题是您正在从主线程和MyThread线程访问变量。 CPU缓存变量并在MyThread的缓存中更新,但不在主线程的缓存中更新,因此您的两个线程永远不会看到其他线程更改。在Java / C等中有关键字volatile,但我不知道它是否存在于PHP中。
我认为你应该尝试在sum synchronized(http://php.net/manual/en/thread.synchronized.php)
中调用方法例如,而不是:
$this->sum->add(5);
呼叫:
$this->synchronized(function($thread){
$thread->sum->add(5);
}, $this);
答案 2 :(得分:0)
我以为我也做错了什么。如上所述[]和其他使用指针的操作将无法正常工作。这是解决方法:
class MyThread extends Thread {
public $table;
public function __construct($data) {
$this->table= $data;
}
public function run(){
//Set temporary array with current data
$temp = $this->table;
for ($i=0; $i < 10; $i++) {
//Add new elements to the array
$temp[] = $i . ' New Value';
}
//Overwrite class variable with temporary array
$this->table = $temp;
}
}
这就像我们在临时函数变量上使用[]操作一样,而不是将它设置为类变量。
更好的例子为什么有必要:
class MyThread extends Thread {
public $table;
public function __construct($data) {
$this->table= $data;
}
public function run(){
$this->addElem();
$temp = $this->table;
$temp[] = 1;
$this->table = $temp;
}
protected function addElem() {
$temp = $this->table;
$temp[] = $i . ' New Value';
$this->table = $temp;
}
}
因此我们可以在多个函数中使用并向变量添加新元素。 这同样适用于+ =和其他运算符,但是你需要临时变量。
易于扩展功能:
protected function overload($name, $val, $type = '[]'){
$temp = $this->$name;
switch($type){
case '[]':
$temp[] = $val;
break;
case '+=':
$temp += $val;
break;
case '.=':
$temp .= $val;
break;
}
$this->$name = $temp;
}
> Eval可以在这里工作,但我觉得这更安全。