Недавно работая с расширением 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
.
Любой объект, не относящийся к определению 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, если он содержит некоторые функциональные возможности и данные, необходимые для других потоков или стеков. Вы должны стремиться использовать как можно меньше потоков и объектов, чтобы выполнить работу.
Ваша проблема в том, что вы получаете доступ к переменной из основного потока и из потока 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);
Я думал, что тоже делаю что-то неправильно. Как упоминалось [], и другие операции с использованием указателей не будут работать. Это обходной путь:
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; }
Эваль мог работать здесь, но я нахожу это намного безопаснее.