Согласованная мультизадачность с помощью генероторов
#dpc #Amsterdam

Писатель и кодер, работающий в SilverStripe.
Кристофер - писатель и кодер, работающий в SilverStripe. Обычно он работает над архитектурой приложений, хотя иногда вы найдете его компиляторами или роботами.

Поговорим о генераторах в PHP и как они могут сделать код асинхронным
$array = ["foo", "bar", "baz"];
foreach ($array as $key => $value) {
print "item: " . $key . "|" . $value . "\n";
}
for ($i = 0; $i < count($array); $i++) {
print "item: " . $i . "|" . $array[$i] . "\n";
}Все начинается с массивов
print is_array($array) ? "yes" : "no"; // yes$document = new DOMDocument();
$document->loadXML("<div></div>");
$elements = $document->getElementsByTagName("div");
print_r($elements); // DOMNodeList Object ( [length] => 1 )print ($elements instanceof Traversable) ? "yes" : "no"; // yesШтуки на-подобии массивов
Классно!
А можно мне так же?
class MyTraversable implements Traversable
{
// тут пишем код...
}PHP Fatal error: Class MyTraversable must implement interface Traversable as part of either Iterator or IteratorAggregate…
Попробуем...
... а так?
class MyIterator implements Iterator
{
// пишем код тутclass MyIterator implements Iterator
{
protected $data;
protected $index = 0;
public function __construct(array $data)
{
$this->data = $data;
}
public function current()
{
return $this->data[$this->index];
}
public function next()
{
return $this->data[$this->index++];
}
public function key()
{
return $this->index;
}
public function rewind()
{
$this->index = 0;
}
public function valid()
{
return $this->index < count($this->data);
}
}Последовательность методов в foreach цикле:
- Перед первой итерацией вызывается Iterator::rewind()
- Перед каждой итерацией вызывается Iterator::valid(
- Если Iterator::valid() вернул false цикл заканчивается
- Если Iterator::valid() вернул true, вызывается Iterator::current()
- Сразу после Iterator::current() вызывается Iterator::key()
- После каждой итерации вызывается Iterator::next() и процесс продолжается с шага 2
$iterator = new MyIterator(["foo", "bar", "baz"]);
foreach ($iterator as $key => $value) {
print "item: " . $key . "|" . $value . "\n";
}А вот это сработает !
Можно проще
class MyIteratorAggregate implements IteratorAggregate
{
protected $data;
public function __construct(array $data)
{
$this->data = $data;
}
public function getIterator()
{
return new ArrayIterator($this->data);
}
}


Так что там насчет генераторов ?
$content = file_get_contents(__FILE__);
$lines = explode("\n", $content);
foreach ($lines as $i => $line) {
print $i . ". " . $line . "\n";
}Вернемся к массивам
function lines($file) {
$handle = fopen($file, "r");
while (!feof($handle)) {
yield trim(fgets($handle));
}
fclose($handle);
}
foreach (lines(__FILE__) as $i => $line) {
print $i . ". " . $line . "\n";
}А теперь сделаем это с помощью генератора
print_r(lines(__FILE__)); // Generator Object ( )Выглядит как итератор
Что там внутри?
print_r(get_class_methods(lines(__FILE__)));
// Array
// (
// [0] => rewind
// [1] => valid
// [2] => current
// [3] => key
// [4] => next
// [5] => send
// [6] => throw
// [7] => __wakeup
// )Метод send
$generator = call_user_func(function() {
yield "foo";
});
print $generator->current() . "\n"; // foo$generator = call_user_func(function() {
$input = (yield "foo");
print "inside: " . $input . "\n";
});
print $generator->current() . "\n";
$generator->send("bar");
Метод throw
$multiply = function ($x, $y) {
yield $x * $y;
};
print $multiply(5, 6)->current(); // 30$calculate = function ($op, $x, $y) use ($multiply) {
if ($op === "multiply") {
$generator = $multiply($x, $y);
return $generator->current();
}
};
print $calculate("multiply", 5, 6); // 30$calculate = function ($op, $x, $y) use ($multiply) {
if ($op === "multiply") {
$generator = $multiply($x, $y);
if (!is_numeric($x) || !is_numeric($y)) {
throw new InvalidArgumentException();
}
return $generator->current();
}
};
print $calculate("multiply", 5, "foo"); // PHP Fatal error...$multiply = function ($x, $y) {
try {
yield $x * $y;
} catch (InvalidArgumentException $exception) {
print "ERRORS!";
}
};
$calculate = function ($op, $x, $y) use ($multiply) {
if ($op === "multiply") {
$generator = $multiply($x, $y);
if (!is_numeric($x) || !is_numeric($y)) {
$generator->throw(new InvalidArgumentException());
}
return $generator->current();
}
};Coroutines - это программные компоненты, которые обобщают подпрограммы для неперехваченной многозадачности, позволяя нескольким точкам входа приостанавливать и возобновлять выполнение в определенных местах

Согласованная многозадачность, также известная как неперехваченная многозадачность, представляет собой стиль многозадачности, в котором операционная система никогда не инициирует переключение контекста из работающего процесса в другой процесс. Вместо этого процессы добровольно дают управление периодически или в режиме ожидания, чтобы одновременно запускать несколько приложений.
Создадим планировщик задач
class Task
{
protected $generator;
public function __construct(Generator $generator)
{
$this->generator = $generator;
}
public function run()
{
$this->generator->next();
}
public function finished()
{
return !$this->generator->valid();
}
}Нам нужна задача
Ну и сам планировщик
class Scheduler
{
protected $queue;
public function __construct()
{
$this->queue = new SplQueue();
}
public function enqueue(Task $task)
{
$this->queue->enqueue($task);
}
public function run()
{
while (!$this->queue->isEmpty()) {
$task = $this->queue->dequeue();
$task->run();
if (!$task->finished()) {
$this->enqueue($task);
}
}
}
}$scheduler = new Scheduler();
$task1 = new Task(call_user_func(function() {
for ($i = 0; $i < 3; $i++) {
print "task 1: " . $i . "\n";
yield;
}
}));
$task2 = new Task(call_user_func(function() {
for ($i = 0; $i < 6; $i++) {
print "task 2: " . $i . "\n";
yield;
}
}));
$scheduler->enqueue($task1);
$scheduler->enqueue($task2);
$scheduler->run();task 1: 0
task 1: 1
task 2: 0
task 2: 1
task 1: 2
task 2: 2
task 2: 3
task 2: 4
task 2: 5class Task
{
protected $generator;
protected $run = false;
public function __construct(Generator $generator)
{
$this->generator = $generator;
}
public function run()
{
if ($this->run) {
$this->generator->next();
} else {
$this->generator->current();
}
$this->run = true;
}
public function finished()
{
return !$this->generator->valid();
}
}RecoilPHP
IcicleIO
Привет!
Согласованная мультизадачность с помощью генероторов
By faecie
Согласованная мультизадачность с помощью генероторов
- 203