Process, thread, coroutine

There are very detailed and rich blogs and learning resources on processes, threads, and coroutines. I won’t go over them here, but I’ll briefly introduce them here.

  1. Processes have their own separate heap and stack, neither sharing heap nor sharing stack, and processes are scheduled by the operating system.

  2. Threads have their own separate stack and shared heap, shared heap, not shared stack, threads are also scheduled by the operating system (standard threads are).

  3. Coroutines share the heap like threads, not the stack, and are scheduled by the programmer in the code of the coroutine.

Coroutines in PHP implement basic yield

The underlying implementation of yield is the generator class, and the iterator class is the implementation of the iterator interface:

Generator implements Iterator {
    public mixed current ( void ) // Returns the currently generated value
    public mixed key ( void ) // Returns the currently generated key
    public void next ( void ) // The generator continues execution
    public void rewind ( void ) // Resets the iterator, which throws an exception if the iteration has already started.
                                             // Renwind's execution will result in the first yield being executed, ignoring its return value.
    public mixed send ( mixed $value ) // Pass a value to the generator as the result of the yield expression, and continue to execute the generator. If when this method is called, the generator
                                            // Instead of a yield expression, it will run to the first yield expression before passing in the value.
    public void throw ( Exception $exception ) // Throw an exception to the generator
    public bool valid ( void ) // Check if the iterator is closed
    public void __wakeup ( void ) // Serialization callback that throws an exception to indicate that the generator cannot be serialized.
}Copy the code

You can refer to the official PHP documentation for the above analysis.

Php.net/manual/zh/c…

And this detailed document translated by Birdman:

www.laruence.com/2015/05/28/…

I’m going to use his implementation of coroutine multitask scheduling as an example and give you some thoughts about what I’ve been doing with blocking.

Example for customizing simple scheduled tasks:

(This example must rely on the coroutine scheduling code implemented by Birdman above)



class timer {
    private $start = 0; // Timed start time
    private $timer; // Interval time difference, in seconds
    private $value = 0; // The resulting value
    private $callback; // asynchronous callback
    private $isEnd = false; // Whether the current timer task ends
    public function __construct($timer,callable $callback)
    {
        $this->start = time();
        $this->timer = $timer;
        $this->callback = $callback;
    }
    public function run(a) {
        if($this->valid()) {
            $callback = $this->callback;
            $callback($this->value ++,$this);
            $this->start = time(); }}/** * Perform the check periodically */
    public function valid(a) {
        $end = time();
        if($end - $this->start >= $this->timer) {
            return true;
        } else {
            return false; }}public function setEnd($isEnd) {
        $this->isEnd = $isEnd;
    }
    public function getEnd(a) {
        return $this->isEnd; }}/** * simulates blocking coroutines 1 ** /
function taskObject1(a) {
    $timer = new timer(1.function($value,timer $timer) {
        if($value >= 5) {
            $timer->setEnd(true);
        }
        echo '<br>'.'A '.$value;
    });
    $tid = (yield getTaskId());
    while (true) {
        if($timer->getEnd() == true) {
            break;
        }
        yield$timer->run(); }}/** * simulates blocking coroutines 2 ** /
function taskObject2(a) {
    $timer = new timer(2.function($value,timer $timer) {
        if($value >= 3) {
            $timer->setEnd(true);
        }
        echo '<br>'.'B '.$value;
    });
    $tid = (yield getTaskId());
    while (true) {
        if($timer->getEnd() == true) {
            break;
        }
        yield $timer->run();
    }
}
$scheduler = new Scheduler;
$scheduler->newTask(taskObject1());
$scheduler->newTask(taskObject2());
$scheduler->run();Copy the code

What is achieved above is:

  1. Generate two tasks, execute them in parallel, and simulate a few seconds of blocking for each task as it executes;

  2. So that the coroutine can switch smoothly when switching, where the task blocking does not affect each other;

Think about:

Why am I doing this? Because I found the coroutine implementation powerful and interesting enough to make multiple tasks parallel, but when I called the system function sleep() in one of the tasks, the blocking task prevented the coroutine from switching, and that’s actually how the coroutine implementation works.

So, I also want to simulate coroutine blocking, but without blocking to see if that works. PHP itself only provides generators to support coroutine calls. If it does not rely on extensions, there is no way to provide multithreaded program implementation, which is not as powerful as Java and can be implemented by child threads.

I was under the impression that Java child threads execute independently and do not block each other, so I was wondering, since PHP can implement a mechanism like multithreading, can it implement non-blocking during calls?

After such implementation and thinking, I fell into a misunderstanding at the beginning, which was caused by the blocking of PHP native function sleep(), that is, to truly achieve non-blocking or asynchronous implementation, must rely on the language layer.

Later, I realized that since a method or function would block during execution, why not change the current method to a custom and make it non-blocking (as opposed to the whole coroutine scheduling)? For example, the above timed execution I implemented one myself.

On the other hand, the purpose of coroutine scheduling itself is to cut the task execution process into as small pieces as possible, so as to quickly switch execution and achieve the purpose of parallelism. In this regard, coroutines should also be considered a programming idea.

Here is an example of a program executed in as small a slice as possible:

// A simple example <? phpfunction xrange($start.$end.$step = 1) {
    for ($i = $start; $i< =$end; $i+ =$step) {
        yield $i;
    }
}

foreach (xrange(1, 1000000) as $num) {
    echo $num."\n";
}Copy the code

In this example, the original way of using range to generate a large array of integers is switched to shard execution, that is, to fetch the specified value during traversal. From the point of view of the code, the memory consumption is very small compared to before.