How to Implement the coroutine in PHP 7, and how to implement the PHP7 coroutine

Source: Internet
Author: User

How to Implement the coroutine in PHP 7, and how to implement the PHP7 coroutine

Preface

I believe everyone has heard of the concept of "coroutine.

However, some people seem to understand this concept, but do not know how to implement it, how to use it, where to use it, or even some people think that yield is a coroutine!

I always believe that if you cannot accurately express a knowledge point, I can think that you just don't understand it.

If you have learned how to use PHP to implement coroutine, you must have read the article: Using coroutine in PHP to implement multi-task scheduling | snow

This article is translated by a foreign author. The translation is concise and clear, and a specific example is given.

The purpose of this article is to make a better supplement to laruence's article. After all, some students still have a poor foundation and are still confused.

What is coroutine

First, let's figure out what a coroutine is.

You may have heard of the "process" and "Thread" concepts.

When the executable file is running in the memory of the computer, it is better that your. EXE file is a class, and the process is the new instance.

A process is the basic unit for the computer system to allocate and schedule resources (the scheduling unit should not tangle with the thread process here). Each CPU can only process one process at a time.

The so-called parallelism only seems to be parallel. In fact, the CPU is switching different processes at a very fast speed.

A system call is required to switch the process. The CPU needs to save all information about the current process, and the CPUCache is also disabled.

Therefore, you do not have to pay for the process switchover.

So how can we achieve "process switching does not have to be paid?

The first condition for a process to be switched is that the process execution is complete, the CPU time slice allocated to the process ends, the system needs to be interrupted, or the process waits for necessary resources (process congestion. You may think that there is nothing to say in the previous situations, but it is a waste if it is blocking and waiting.

In fact, if the program is blocked, there are other executable places to execute. It is not necessary to be silly!

So there is a thread.

A thread is simply a "microprocess" and runs a function (logical flow ).

So we can use threads to reflect the functions that can be run simultaneously during programming.

There are two types of threads. One is managed and scheduled by the kernel.

We said that as long as the kernel is involved in management and scheduling, the cost is very high. This thread actually solves the problem of blocking a thread in a process. We can schedule another runable thread to run, but it is still in the same process, so there is no process switchover.

Another thread, whose scheduling is managed by the programmer's own program, is invisible to the kernel. This thread is called a user space thread 』.

Coroutine can be understood as a user space thread.

Coroutine has several features:

  • Collaboration, because it is a scheduling policy written by the programmer, It switches through collaboration rather than Preemption
  • Create, switch, and destroy the user State
  • ⚠From a programming perspective, the idea of coroutine is essentially the active delivery and recovery mechanisms of the control flow.
  • Iterators are often used to implement coroutine

Now, you should understand the basic concepts of coroutine?

PHP implementation coroutine

Let's start with explaining the concept step by step!

Iteratable object

PHP5 provides a way to define an object so that it can be traversed through the unit list, for example, using a foreach statement.

To implement an iteratable object, you need to implement the Iterator interface:

<?phpclass MyIterator implements Iterator{ private $var = array(); public function __construct($array) {  if (is_array($array)) {   $this->var = $array;  } } public function rewind() {  echo "rewinding\n";  reset($this->var); } public function current() {  $var = current($this->var);  echo "current: $var\n";  return $var; } public function key() {  $var = key($this->var);  echo "key: $var\n";  return $var; } public function next() {  $var = next($this->var);  echo "next: $var\n";  return $var; } public function valid() {  $var = $this->current() !== false;  echo "valid: {$var}\n";  return $var; }}$values = array(1,2,3);$it = new MyIterator($values);foreach ($it as $a => $b) { print "$a: $b\n";}

Generator

It can be said that in order to have an object that can be traversed by foreach, You have to implement a bunch of methods. The yield keyword is to simplify this process.

The generator provides an easier way to implement simple object iteration. Compared with defining classes that implement the Iterator interface, the performance overhead and complexity are greatly reduced.

<?phpfunction xrange($start, $end, $step = 1) { for ($i = $start; $i <= $end; $i += $step) {  yield $i; }}foreach (xrange(1, 1000000) as $num) { echo $num, "\n";}

Remember, if yield is used in a function, it is useless to directly call it. It cannot be equivalent to executing a function!

Therefore, yield is yield. Who will say that yield is a coroutine next time.

PHP coroutine

As mentioned above, the coroutine requires programmers to write the scheduling mechanism by themselves. Let's look at how to write this mechanism.

0) correct use of the generator

Since the generator cannot be called directly like a function, how can it be called?

The method is as follows:

  • Foreach him
  • Send ($ value)
  • Current/next...

1) Task implementation

A Task is an abstraction of a Task. As we have just mentioned, a coroutine is a user space coroutine. A thread can be understood as a function.

Therefore, the constructor of a Task receives a closure function, which is named coroutine.

/*** Task class */class Task {protected $ taskId; protected $ coroutine; protected $ beforeFirstYield = true; protected $ sendValue;/*** Task constructor. * @ param $ taskId * @ param Generator $ coroutine */public function _ construct ($ taskId, Generator $ coroutine) {$ this-> taskId = $ taskId; $ this-> coroutine = $ coroutine;}/*** get the ID of the current Task ** @ return mixed */public function getTaskId () {return $ thi S-> taskId;}/*** determines if the Task has been executed. ** @ return bool */public function isFinished () {return! $ This-> coroutine-> valid ();}/*** sets the value to be passed to the coroutine next time, for example, $ id = (yield $ xxxx ), this value is assigned to $ id ** @ param $ value */public function setSendValue ($ value) {$ this-> sendValue = $ value ;} /*** run the task ** @ return mixed */public function run () {// note that the generator will be reset at the beginning, therefore, the first value must use current to obtain if ($ this-> beforeFirstYield) {$ this-> beforeFirstYield = false; return $ this-> coroutine-> current ();} else {// as we have said, use send to call a generator $ retval = $ this-> coroutine-> send ($ this-> sendValue ); $ this-> sendValue = null; return $ retval ;}}}

2) Implementation of Scheduler

The next step is Scheduler, which plays the role of Scheduler.

/*** Class Scheduler */Class schedted {/*** @ var SplQueue */protected $ taskQueue;/*** @ var int */protected $ tid = 0; /*** Scheduler constructor. */public function _ construct () {/* The principle is to maintain a queue. * As mentioned earlier, from the programming perspective, the idea of coroutine is essentially the control flow proactive delivery (yield) and recovery (resume) Mechanisms **/$ this-> taskQueue = new SplQueue ();} /*** Add a task ** @ param Generator $ task * @ return int */public function addTask (Generator $ task) {$ Tid = $ this-> tid; $ task = new Task ($ tid, $ task); $ this-> taskQueue-> enqueue ($ task ); $ this-> tid ++; return $ tid;}/*** enter the Task into the queue ** @ param task $ Task */public function schedule (task $ Task) {$ this-> taskQueue-> enqueue ($ task);}/*** run scheduler */public function run () {while (! $ This-> taskQueue-> isEmpty () {// task Team-out $ task = $ this-> taskQueue-> dequeue (); $ res = $ task-> run (); // run the task until yield if (! $ Task-> isFinished () {$ this-> schedule ($ task); // if the task has not been fully executed, wait for the next execution }}}}

In this way, we basically implement a coroutine scheduler.

You can use the following code to test:

<? Phpfunction task1 () {for ($ I = 1; $ I <= 10; ++ $ I) {echo "This is task 1 iteration $ I. \ n "; yield; // proactively giving the execution right of the CPU} function task2 () {for ($ I = 1; $ I <= 5; ++ $ I) {echo "This is task 2 iteration $ I. \ n "; yield; // proactively give the execution right of the CPU} $ scheduler = new Scheduler; // instantiate a scheduler $ schedtask-> newTask (task1 ()); // Add different closure functions as tasks $ scheduler-> newTask (task2 (); $ scheduler-> run ();

The key is where to get the PHP coroutine.

Function task1 () {/* There is a remote task, which takes 10 seconds. It may be a task for a remote machine to capture and analyze remote URLs, we only need to submit the final result to the remote machine to get the result */remote_task_commit (); // after the request is sent, we should not wait here and take the initiative to give the CPU execution right to task 2 to run, he does not rely on this result yield; yield (remote_task_receive ());...} function task2 () {for ($ I = 1; $ I <= 5; ++ $ I) {echo "This is task 2 iteration $ I. \ n "; yield; // proactively giving the execution right of the CPU }}

This improves the execution efficiency of the program.

As for the implementation of "system call", I will not describe it here.

3) coroutine Stack

Another example of coroutine stack is provided.

As we have mentioned above, if yield is used in a function, it cannot be used as a function.

So you nested another coroutine function in a coroutine function:

<?phpfunction echoTimes($msg, $max) { for ($i = 1; $i <= $max; ++$i) {  echo "$msg iteration $i\n";  yield; }}function task() { echoTimes('foo', 10); // print foo ten times echo "---\n"; echoTimes('bar', 5); // print bar five times yield; // force it to be a coroutine}$scheduler = new Scheduler;$scheduler->newTask(task());$scheduler->run();

The echoTimes here cannot be executed! Therefore, the coroutine stack is required.

But it doesn't matter. Let's change the code we just got.

Change the initialization method in the Task, because when running a Task, we need to analyze which sub-coroutines it contains, and then save the sub-coroutines with a stack. (Good C language students can naturally understand this. If they do not understand it, I suggest that you solve the process's memory model and how to handle function calls)

/*** Task constructor. * @ param $ taskId * @ param Generator $ coroutine */public function _ construct ($ taskId, Generator $ coroutine) {$ this-> taskId = $ taskId; // $ this-> coroutine = $ coroutine; // replace it with this. The actual Task-> run is the stackedCoroutine function, not the closure function saved by $ coroutine $ this-> coroutine = stackedCoroutine ($ coroutine );}

When Task-> run (), a loop is used for analysis:

/*** @ Param Generator $ gen */function stackedCoroutine (Generator $ gen) {$ stack = new SplStack; // continuously traverse the passed Generator (;;) {// $ gen can be understood as pointing to the currently running coroutine closure function (generator) $ value = $ gen-> current (); // getting the breakpoint, that is, the yield value if ($ value instanceof Generator) {// if it is also a Generator, This is the sub-coroutine, save the currently running coroutine into the stack $ stack-> push ($ gen); $ gen = $ value; // send the subcoroutine function to gen and continue execution, note that the next step is to execute the sub-coroutine process;} // we encapsulate the results returned by the sub-coroutine. The following describes $ isReturnValue = $ val Ue instanceof CoroutineReturnValue; // The Sub-coroutine returns '$ value'. You need the master coroutine to help process if (! $ Gen-> valid () | $ isReturnValue) {if ($ stack-> isEmpty () {return;} // if gen has been executed, or the sub-coroutine must return a value to the master coroutine to process $ gen = $ stack-> pop (); // The output stack, get the master coroutine $ gen-> send ($ isReturnValue? $ Value-> getValue (): NULL); // call the main coroutine to process the output value of the sub-coroutine;} $ gen-> send (yield $ gen-> key () =>$ value); // continue to execute the sub-coroutine }}

Then we add the end mark of echoTime:

Class CoroutineReturnValue {protected $ value; public function _ construct ($ value) {$ this-> value = $ value;} // you can obtain the output value of a sub-coroutine to the master coroutine, public function getValue () {return $ this-> value ;}} function retval ($ value) {return new CoroutineReturnValue ($ value );}

Then modify the echoTimes:

Function echoTimes ($ msg, $ max) {for ($ I = 1; $ I <= $ max; ++ $ I) {echo "$ msg iteration $ I \ n"; yield;} yield retval (""); // Add this as the end mark}

Task changed:

function task1(){ yield echoTimes('bar', 5);}

In this way, a coroutine stack is implemented, and now you can give a similar picture.

4) the yield from keyword in PHP7

Yield from is added in PHP7, so we don't need to implement the Ctrip stack by ourselves. It's really great.

Change the Task constructor back:

Public function _ construct ($ taskId, Generator $ coroutine) {$ this-> taskId = $ taskId; $ this-> coroutine = $ coroutine; // $ this-> coroutine = stackedCoroutine ($ coroutine); // you do not need to implement it yourself. Change it back to the previous one}

EchoTimes function:

function echoTimes($msg, $max) { for ($i = 1; $i <= $max; ++$i) {  echo "$msg iteration $i\n";  yield; }}

Task1 generator:

function task1(){ yield from echoTimes('bar', 5);}

In this way, sub-coroutine can be easily called.

Summary

Now I should understand how to implement the PHP coroutine?

Well, the above is all the content of this article. I hope the content of this article has some reference and learning value for everyone's learning or work. If you have any questions, please leave a message to us, thank you for your support.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.