Pthread Объекты темы reset их состояние

Работая недавно с расширением 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() не является ожидаемым 5 10 15 20 25 30 35 40 45 50 но 0 0 0 0 0 0 0 0 0 0. Никто не вмешивается в поток - почему он не сохраняет свое состояние?


Боковое примечание: если я не запускаю поток, а вместо этого вызываю метод run() непосредственно в основном потоке ($thread->run();), результат остается тем же. Но если теперь удалить extends Thread в объявлении класса, он отлично работает и возвращает ожидаемый 5 10 15 20 25 30 35 40 45 50.

Ответ 1

Любой объект, не относящийся к определению 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, если он содержит некоторые функциональные возможности и данные, необходимые для других потоков или стеков. Вы должны стремиться использовать как можно меньше потоков и объектов, чтобы выполнить работу.

Ответ 2

Ваша проблема в том, что вы получаете доступ к переменной из основного потока и из потока MyThread. CPU кэширует переменную, и она обновляется в кеше для MyThread, но не в кеше для основного потока, поэтому ваши обои не видят изменений нитей других. В Java/C и т.д. Есть ключевое слово volatile, но я не знаю, существует ли это в PHP.

Я думаю, вы должны попытаться вызвать методы в сумме, синхронизированной (http://php.net/manual/en/thread.synchronized.php)

Например, вместо:

        $this->sum->add(5);

Вызов:

        $this->synchronized(function($thread){
            $thread->sum->add(5);
        }, $this);

Ответ 3

Я думал, что делаю что-то неправильно. Как упоминалось [], и другие операции с использованием указателей не будут работать. Это обходной путь:

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 может работать здесь, но я нахожу это намного более безопасным.