Analysis of PHP's multi-tasking process

Source: Internet
Author: User
Tags rewind throw exception pagekit silverstripe
This article mainly introduces the multi-tasking process of PHP, has a certain reference value, now share to everyone, the need for friends can refer to

So, let's get started!

This is the question we are going to discuss in this article. But we'll start with a simpler and more familiar example.

Everything starts with the array

We can use arrays by simply repeating them:

$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";}

This is the basic implementation that our daily coding relies on. You can get the key names and key values for each element by iterating through the array.

Of course, if we want to be able to know when to use arrays. PHP provides a handy built-in function:

Print Is_array ($array)? "Yes": "No"; Yes

Class array Processing

Sometimes we need to iterate over some data in the same way, but they are not array types. For example, the DOMDocument class is processed:

$document = new DOMDocument (); $document->loadxml ("<p></p>"); $elements = $document getElementsByTagName ("P");p Rint_r ($elements); Domnodelist Object ([length] = 1)

This is obviously not an array, but it has a length property. Can we traverse an array as if it were traversed? We can tell if it implements the following special interface:

Print ($elements instanceof traversable)? "Yes": "No"; Yes

This is really very useful. It does not cause us to trigger an error while traversing non-ergodic data. We only need to detect before processing.

However, this raises another question: Can we let the custom class also have this function? The answer is YES! The first implementation method is similar to the following:

Class Mytraversable implements traversable{    //  coding here ...}

If we execute this class, we will see an error message:

PHP Fatal Error:class mytraversable must implement Interface traversable as part of either Iterator or Iteratoraggr Egate

Iterator (iterator)

We can't directly implement traversable, but we can try the second scenario:

Class Mytraversable implements iterator{    //  coding here ...}

This interface requires us to implement 5 methods. Let's refine our iterators:

Class Mytraversable implements iterator{    protected $data;    protected $index = 0;    Public function __construct ($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);}    }

Here are a few things we need to pay attention to:

    1. We need to store the $data array passed in by the constructor method so that later we can get its elements from it.

    2. An internal index (or pointer) is also required to track the current or next element.

    3. Rewind () simply resets the index property so that current () and next () work correctly.

    4. The key name is not only a numeric type! The array index is used here to ensure that the example is simple enough.

We can run this code to the following:

$iterator = new Myiterator (["foo", "Bar", "Baz"]); foreach ($iterator as $key = + $value) {    print "item:". $key. "|" . $value. "\ n";}

This seems to require too much work, but it is a neat implementation that can use the foreach/for function like an array.

Iteratoraggregate (Aggregate iterator)

Do you remember the traversable exception thrown by the second interface? Let's look at a faster implementation than implementing the Iterator interface:

Class Myiteratoraggregate implements iteratoraggregate{    protected $data;    Public function __construct ($data)    {        $this->data = $data;    }    Public Function Getiterator ()    {        return new Arrayiterator ($this->data);}    }

Here we are cheating. Compared to the realization of a complete Iterator, we decorate by arrayiterator () . However, this simplifies a lot of code compared to implementing a complete Iterator .

Brother Mo-Hurry! Let's compare some code first. First, we read each row of data from a file without using the generator:

$content = file_get_contents (__file__), $lines = explode ("\ n", $content), foreach ($lines as $i + = $line) {    print $i . ". " . $line. "\ n";}

This code reads the file itself, and then prints out the line number and code for each line. So why don't we use generators?

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";}

I know this looks more complicated. Yes, but that's because we didn't use the file_get_contents () function. A generator looks like a function, but it will stop running every time it gets to the yield keyword.

The generator looks a bit like an iterator:

Print_r (lines (__file__)); Generator Object ()

Although it is not an iterator, it is a Generator. What method does it define internally?

Print_r (Get_class_methods (lines (__file__)));  array//(//     [0] = rewind//     [1] = valid//     [2] = current//     [3] = = key//     [4] = = next//     [5] = send//     [6] = throw//     [7] = __wakeup//)
If you read a large file and then use memory_get_peak_usage (), you will notice that the generator code will use fixed memory regardless of how large the file is. It goes one line at a time. Instead, the entire file is read with the file_get_contents () function, and larger memory is used. This is the advantage that the generator can bring to us when iterating over such things!

Send (sending data)

Data can be sent to the generator. Look at the following generator:

<?php$generator = Call_user_func (function () {    yield "foo";}); Print $generator->current (). "\ n"; Foo
Note here How do we encapsulate the generator function in the call_user_func () function? Here is just a simple function definition and then immediately call it to get a new generator instance
...

We have seen the use of yield . We can extend this generator to receive data:

$generator = Call_user_func (function () {    $input = (yield "foo");    Print "Inside:". $input. "\ n";}); Print $generator->current (). "\ n"; $generator->send ("Bar");

Data is passed in and returned through the yield keyword. First, execute the current () code until yieldis encountered, returning foo. Send () outputs the output to the generator print input location. You need to get used to this usage.

Throw exception (throw)

Because we need to interact with these functions, we may want to push exceptions to the generator. This allows these functions to handle exceptions on their own.

Take a look at the following example:

$multiply = function ($x, $y) {    yield $x * $y;}; Print $multiply (5, 6)->current (); 30

Now let's wrap it up in another function:

$calculate = function ($op, $x, $y) use ($multiply) {    if ($op = = = ' Multiply ') {        $generator = $multiply ($x, $y); 
  return $generator->current ();    }}; Print $calculate ("Multiply", 5, 6); 30

Here we encapsulate the multiplication generator with a common closure. Now let's verify the invalid parameters:

$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 ...

What if we want to be able to handle exceptions through the generator? How can we pass an exception to the generator?

$multiply = function ($x, $y) {    try {        yield $x * $y;    } catch (InvalidArgumentException $exception) {        prin T "errors!";    }}; $calculate = function ($op, $x, $y) use ($multiply) {    if ($op = = = "Multiply") {        $generator = $multiply ($x, $y); C14/>if (!is_numeric ($x) | |!is_numeric ($y)) {            $generator->throw (New InvalidArgumentException ());        }        return $generator->current ();    }}; Print $calculate (' Multiply ', 5, ' foo '); PHP Fatal Error ...

It's a good stay! Not only can we use generators like iterators. You can also send data through them and throw exceptions. They are interruptible and recoverable functions. Some languages call these functions ...

We can use the Coroutines to build asynchronous code. Let's create a simple task scheduler. First we need a Task class:

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 ();}    }

A Task is an adorner for a common generator. We assign the generator to its member variables for subsequent use, and then implement a simple run () and finished () method. The Run () method is used to perform the task, and thefinished () method is used to let the scheduler know when to terminate the run.

Then we need a Scheduler class:

Class scheduler{    protected $queue;    Public function __construct ()    {        $this->queue = new Splqueue ();    }    Public Function Enqueue (Task $task)    {        $this->queue->enqueue ($task);    }    Pulic function Run ()    {while        (! $this->queue->isempty ()) {            $task = $this->queue->dequeue () ;            $task->run ();            if (! $task->finished ()) {                $this->queue->enqueue ($task);}}}    

The Scheduler is used to maintain a queue of tasks to be executed. run () pops up all the tasks in the queue and executes it until the entire queue task is completed. If a task is not finished, we will into row again when this task is completed.

splqueue is suitable for this example. It is a FIFO (first-in-one-out: fist) data structure that ensures that each task is able to get enough processing time.

We can run this code like this:

$scheduler = new Scheduler (); $task 1 = new Task (function () {for    ($i = 0; $i < 3; $i + +) {        print " Task1: ". $i. "\ n";        (yield;    })); $task 2 = new Task (Call_user_func () (function () {for    ($i = 0; $i < 6; $i + +) {        print "Task2:". $i. "\ n";        (yield;    })); $scheduler->enqueue ($task 1); $scheduler->enqueue ($task 2); $scheduler->run ();

At run time, we will see the following execution results:

Task 1:0task 1:1task 2:0task 2:1task 1:2task 2:2task 2:3task 2:4task 2:5

This is almost the result of the execution we want. But one problem occurs when each task is run for the first time, and they are executed two times. We can fix this problem by making a slight modification to the Task class:

Class 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 ();}    }

We need to adjust the first run () method call from the generator's currently valid pointer read run. Subsequent calls can be read from the next pointer run ...

Some people have implemented some awesome libraries based on this idea. Let's take a look at two of them ...

recoilphp

Recoilphp is a set of association-based class libraries that are most impressive for the reactphp kernel. Event loops can be exchanged between recoilphp and recoilphp, and your program does not require schema adjustments.

Let's take a look at the reactphp asynchronous DNS solution:

function Resolve ($domain, $resolver) {    $resolver        ->resolve ($domain)        ->then (function ($IP) use ($ domain) {            print "domain:". $domain. "\ n";            Print "IP:". $ip. "\ n";        }, Function ($error) {                        print $error. "\ n";        })} function run () {    $loop = React\eventloop\factory::create ();     $factory = new React\dns\resolver\factory ();     $resolver = $factory->create ("8.8.8.8", $loop);     Resolve ("silverstripe.org", $resolver);    Resolve ("wordpress.org", $resolver);    Resolve ("wardrobecms.com", $resolver);    Resolve ("pagekit.com", $resolver);     $loop->run ();} Run ();

Resolve () receives the domain name and DNS resolver and uses reactphp to perform a standard DNS lookup. Don't be too tangled with the inside of the resolve () function. The important thing is that this function is not a generator, but a function!

run () creates a reactphp event loop, and the DNS resolver (here is a factory instance) resolves several domain names. Again, this is not a generator.

Want to know how recoilphp is different? Also want to know more details!

Use Recoil\recoil; function Resolve ($domain, $resolver) {    try {        $ip = (yield $resolver->resolve ($domain));         Print "Domain:". $domain. "\ n";        Print "IP:". $ip. "\ n";    } catch (Exception $exception) {        print $exception->getmessage (). "\ n";    }} function run () {    $loop = (yield recoil::eventloop ());     $factory = new React\dns\resolver\factory ();     $resolver = $factory->create ("8.8.8.8", $loop);     Yield [        Resolve ("silverstripe.org", $resolver),        Resolve ("wordpress.org", $resolver),        Resolve (" Wardrobecms.com ", $resolver),        Resolve (" pagekit.com ", $resolver),    ];} Recoil::run ("Run");

Do some amazing work by integrating it into the reactphp. Each time resolve () is run, Recoilphp manages the Promise object returned by the $resoler->resolve () and sends the data to the generator. At this point we are like writing synchronous code. Unlike the callback code we use in the other one-step model, there is only one list of instructions.

Recoilphp knows that it should manage a yield array that is returned when the run () function is executed. Roceilphp also supports co-based database (PDO) and log libraries.

Icicleio

Icicleio achieves reactphp-like goals for a completely new solution, but only uses the co-process functionality. It contains only a few components compared to reactphp. However, the core of the asynchronous flow, server, Socket, event loop characteristics one does not fall.

Let's look at a socket server example:

Use Icicle\coroutine\coroutine;use icicle\loop\loop;use icicle\socket\client\clientinterface;use Icicle\Socket\ Server\serverinterface;use icicle\socket\server\serverfactory; $factory = new Serverfactory ();         $coroutine = Coroutine::call (function (Serverinterface $server) {$clients = new splobjectstorage (); $handler = Coroutine::async (function (clientinterface $client) use (& $clients) {$clients->attac                         H ($client);            $host = $client->getremoteaddress ();                         $port = $client->getremoteport (); $name = $host. ":" .                         $port;                        try {foreach ($clients as $stream) {if ($client!== $stream) { $stream->write ($name.                    "connected.\n"); }} yield $client->write ("Welcome". $name.                                 "!\n"); while ($client->isreadable ()) {$data = Trim (yield $client->read ());                    if ("/exit" = = = $data) {yield $client->end ("goodbye!\n"); } else {$message = $name. ":" . $data.                                                "\ n"; foreach ($clients as $stream) {if ($client!== $stream) {$stre                            Am->write ($message);                }}}}} catch (Exception $exception) {            $client->close ($exception);                } finally {$clients->detach ($client); foreach ($clients as $stream) {$stream->write ($name.                "disconnected.\n");         }            }        }    );    while ($server->isopen ()) {$handler (yield $server->accept ()); }}, $factory->create ("127.0.0.1", 6000)); Loop::run ();

As far as I know, this code does the following things:

    1. Create a server instance on 127.0.0.1 and port 6000, and then pass it to the external generator.

    2. The external generator runs while the server waits for a new connection. When the server receives a connection it passes it in to the internal generator.

    3. The internal generator writes a message to the socket. Run when the socket is readable.

    4. Each time the socket sends a message to the server, the internal generator detects whether the message is an exit identity. If yes, notify the other socket. Otherwise, the other socket sends the same message.

Open command line terminal input NC localhost 6000 view execution results!

The example uses Splobjectstorage to track the socket connection. This allows us to send messages to all sockets.

This topic can contain a lot of content. Hopefully you'll see how the generators are created, and how they can help write iterators and asynchronous code.

If you have any questions, you can ask me at any time.

Related Article

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.