8

最近在使用扩展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() . " ";
        }
    }
}

在我的主函数中,我创建了一个 Sum 对象,将其注入线程并启动它:

$sum = new Sum();
$thread = new MyThread($sum);
$thread->start();
$thread->join();
echo $sum->getValue();

我预计结果是50,因为线程必须将值增加 10 倍 5。但我得到了一个0

更奇怪的是,不是同步回主线程失败,而是线程似乎在途中忘记了它的内部状态:run()方法内部的 echo 输出不是预期的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.

4

3 回答 3

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 不使用指针,您可以选择在连接后从线程对象中检索引用。

这些都是简单的操作,您不需要同步。您应该同步的唯一时间是您计划等待一个对象或通知一个对象。

与 pthread 捆绑的对象非常适合这种环境,并且永远不会被序列化。

请务必阅读手册中的介绍以及您希望使用的方法中的所有示例,以确切了解什么是什么,然后随时问为什么:)

我知道 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 对象包含其他线程或堆栈所需的某些功能和数据,则传递它并没有错。您应该旨在使用尽可能少的线程和对象来完成工作。

于 2013-01-28T15:40:17.220 回答
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);
于 2013-01-28T14:39:40.453 回答
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 可以在这里工作,但我发现这更安全。

于 2014-08-31T23:43:26.907 回答