PHP5.5 A better new feature is the implementation of support for generators and co-programs. For generators, PHP documentation and a variety of other blog posts (like this one or this one) have been explained in great detail. Collaborative programs are less concerned with each other, so the collaborative program has a very powerful function but it is difficult to be known, and it is difficult to explain.
This article guides you through the use of collaborative procedures to implement task scheduling, through the implementation of an example of technical understanding. I'll do a simple background introduction in the first few sections. If you already have a good base, you can jump straight to the "collaborative multitasking" section.
Generator
Generator The basic idea is also a function, the return value of this function is output in turn, instead of just returning a single value. Or, in other words, the generator makes it easier for you to implement an iterator interface. The following is a simple explanation by implementing a xrange function:
Copy the Code code as follows:
function xrange ($start, $end, $step = 1) {
for ($i = $start; $i <= $end; $i + = $step) {
Yield $i;
}
}
foreach (xrange (1, 1000000) as $num) {
echo $num, "\ n";
}
The xrange () function above provides the same functionality as the PHP built-in function, range (). But the difference is that the range () function returns an array containing the group values from 1 to 1 million (note: see the manual). The xrange () function returns an iterator that outputs these values sequentially, and does not actually calculate in the form of an array.
The advantages of this approach are obvious. It allows you to handle big data collections without having to load them into memory at once. You can even handle infinite streams of data.
Of course, it is possible to implement this function differently through the generator, but it can be implemented by inheriting the iterator interface. It is more convenient to implement it by using a generator without having to implement the 5 methods in the iterator interface.
The generator is an interruptible function
It is important to understand the synergy from the builder and how it works internally: The generator is an interruptible function, in which the yield forms the break point.
Immediately following the example above, if you call Xrange (1,1000000), the code in the Xrange () function does not actually run. Instead, PHP simply returns an instance of the generator class that implements the iterator interface:
Copy the Code code as follows:
$range = xrange (1, 1000000);
Var_dump ($range); Object (Generator) #1
Var_dump ($range instanceof Iterator); BOOL (TRUE)
You call an iterator method on an object once, where the code runs once. For example, if you call $range->rewind (), then the code in Xrange () runs to where the control flow first appears yield. In this case, this means that the yield $i will run when the $i= $start. The value passed to the yield statement is obtained using $range->current ().
In order to continue executing the code in the generator, you must call the $range->next () method. This will start the generator again until the yield statement appears. Therefore, successive calls to the next () and current () methods will enable you to get all the values from the generator until the yield statement is no longer present at a point. For Xrange (), this situation occurs when $i exceeds $end. In this case, the control flow will reach the end of the function, so no code will be executed. Once this happens, the Vaild () method returns False, at which point the iteration ends.
co-process
The main thing that the co-process adds to the functionality above is the ability to send the data back to the generator. This transforms the one-way communication from the generator to the caller into two-way communication between the two.
Pass the data to the coprocessor by calling the generator's send () method instead of its next () method. The following logger () coprocessor is an example of how this communication works:
Copy the Code code as follows:
function Logger ($fileName) {
$fileHandle = fopen ($fileName, ' a ');
while (true) {
Fwrite ($fileHandle, yield. "\ n");
}
}
$logger = Logger (__dir__. '/log ');
$logger->send (' Foo ');
$logger->send (' Bar ')
As you can see, yield is not used as a statement, but as an expression. That is, it has a return value. The return value of yield is the value passed to the Send () method. In this example, yield will return "Foo" first, and then return "Bar".
In the above example, yield is only used as the receiver. It is possible to mix two usages, which can be either received or sent. Examples of how incoming and outgoing communications are performed are as follows:
Copy the Code code as follows:
Function Gen () {
$ret = (yield ' yield1 ');
Var_dump ($ret);
$ret = (yield ' yield2 ');
Var_dump ($ret);
}
$gen = Gen ();
Var_dump ($gen->current ()); String (6) "Yield1"
Var_dump ($gen->send (' Ret1 ')); String (4) "Ret1" (the first var_dump in Gen)
String (6) "Yield2" (the Var_dump of the->send () return value)
Var_dump ($gen->send (' Ret2 ')); String (4) "Ret2" (again from within Gen)
NULL (The return value of->send ())
It's a bit difficult to understand the exact order of the output right away, so make sure you know what to do in this way. I would like to highlight two points: 1th, the use of parentheses on both sides of the yield expression is not accidental. For technical reasons (although I have considered adding an exception to the assignment, as in Python), parentheses are required. 2nd, you may have noticed that the call to current () did not call Rewind (). If this is done, the rewind operation has been implicitly performed.
Multi-tasking collaboration
If you read the logger () example above, then you think "Why should I use a co-process for two-way communication?" Why can't I just use a common class? "That's exactly right for you to ask. The above example shows the basic usage, but the context does not really show the advantages of using the coprocessor. This is the rationale for enumerating many of the examples of co-processes. As mentioned in the previous introduction, the process is a very powerful concept, but such applications are rare and often complex. It is difficult to give some simple and true examples.
In this article, what I decided to do is to use the process to achieve multi-task collaboration. The problem we try to solve is that you want to run multiple tasks (or "programs") concurrently. However, the processor can only run one task at a time (the goal of this article is not to consider multicore). So the processor needs to switch between the different tasks and always let each task run "for a while."
Collaboration in the term multi-tasking demonstrates how to do this: it requires that the currently running task automatically pass control back to the scheduler so that it can run other tasks. This is the opposite of preemption multitasking: The scheduler can interrupt tasks that have been running for a while, whether they like it or not. Collaboration multitasking is used in earlier versions of Windows (WINDOWS95) and Mac OS, but they later switch to using preemptive multitasking. The reason is quite clear: if you rely on the program to automatically return control, then the bad behavior of the software will be easy for itself to occupy the entire CPU, not to share with other tasks.
At this point you should understand the link between the process and the Task Scheduler: The yield directive provides a way for the task to break itself, and then passes the control to the scheduler. Therefore, the process can run several other tasks. Further, yield can be used to communicate between tasks and schedulers.
Our goal is to use a more lightweight wrapper for the "task" of the process function:
Copy the Code code as follows:
Class Task {
protected $taskId;
protected $coroutine;
protected $sendValue = null;
protected $beforeFirstYield = true;
Public function __construct ($taskId, Generator $coroutine) {
$this->taskid = $taskId;
$this->coroutine = $coroutine;
}
Public Function GetTaskID () {
return $this->taskid;
}
Public Function Setsendvalue ($sendValue) {
$this->sendvalue = $sendValue;
}
Public Function run () {
if ($this->beforefirstyield) {
$this->beforefirstyield = false;
return $this->coroutine->current ();
} else {
$retval = $this->coroutine->send ($this->sendvalue);
$this->sendvalue = null;
return $retval;
}
}
Public Function isfinished () {
Return! $this->coroutine->valid ();
}
}
A task is to mark a process with a task ID. Using the Setsendvalue () method, you can specify which values will be sent to the next recovery (after which you will learn that we need this). The run () function does not really do anything except calling the co-program of the Send () method. To understand why adding beforefirstyieldflag, you need to consider the following code snippet:
Copy the Code code as follows:
Function Gen () {
Yield ' foo ';
Yield ' bar ';
}
$gen = Gen ();
Var_dump ($gen->send (' something '));
As the Send () happens before the first yield there is a implicit rewind () call,
So what really happens are this:
$gen->rewind ();
Var_dump ($gen->send (' something '));
The rewind () would advance to the first yield (and ignore its value), the Send () would
Advance to the second yield (and dump its value). Thus we loose the first yielded value!
By adding beforefirstyieldcondition we can determine that the value of first yield is returned.
The scheduler now has to do a little more than a multi-tasking loop before running multiple tasks:
Copy the Code code as follows:
Class Scheduler {
protected $maxTaskId = 0;
protected $TASKMAP = []; TaskId = Task
protected $taskQueue;
Public Function __construct () {
$this->taskqueue = new Splqueue ();
}
Public Function NewTask (Generator $coroutine) {
$tid = + + $this->maxtaskid;
$task = new Task ($tid, $coroutine);
$this->taskmap[$tid] = $task;
$this->schedule ($task);
return $tid;
}
Public Function Schedule (Task $task) {
$this->taskqueue->enqueue ($task);
}
Public Function run () {
while (! $this->taskqueue->isempty ()) {
$task = $this->taskqueue->dequeue ();
$task->run ();
if ($task->isfinished ()) {
unset ($this->taskmap[$task->gettaskid ()]);
} else {
$this->schedule ($task);
}
}
}
}
The NewTask () method (using the next idle task ID) creates a new task and then puts the task into the task map array. It then implements the task scheduling by putting the task into the task queue. The run () method then scans the task queue and runs the task. If a task is finished, it will be removed from the queue or it will be dispatched again at the end of the queue.
Let's take a look at the scheduler with two simple (and meaningless) tasks:
Copy the Code code as follows:
function Task1 () {
for ($i = 1; $i <=; + + $i) {
echo "This is Task 1 iteration $i. \ n";
Yield
}
}
function Task2 () {
for ($i = 1; $i <= 5; + + $i) {
echo "This is Task 2 iteration $i. \ n";
Yield
}
}
$scheduler = new Scheduler;
$scheduler->newtask (Task1 ());
$scheduler->newtask (Task2 ());
$scheduler->run ();
Both tasks simply echo one message and then use yield to pass control back to the scheduler. The output results are as follows:
Copy the Code code as follows:
This is task 1 iteration 1.
This is task 2 iteration 1.
This is Task 1 iteration 2.
This is Task 2 iteration 2.
This is Task 1 Iteration 3.
This is Task 2 iteration 3.
This is Task 1 iteration 4.
This is Task 2 iteration 4.
This is Task 1 iteration 5.
This is Task 2 iteration 5.
This is Task 1 iteration 6.
This is Task 1 iteration 7.
This is Task 1 iteration 8.
This is Task 1 Iteration 9.
This is Task 1 iteration 10.
The output is exactly what we expected: for the first five iterations, the two tasks are running alternately, and then the second task ends, only the first task continues to run.
communicating with the scheduler
Now that the scheduler is running, we turn to the next item in the timeline: communication between the task and the scheduler. We will use the same way that the process uses to communicate with the operating system session: System calls. The reason we need a system call is that the operating system is at a different permission level than the process. So in order to perform privileged operations (such as killing another process), the control has to be passed back to the kernel in some way, so the kernel can do what it says. Again, this behavior is done internally by using interrupt instructions. Used in the past is the generic INT directive, which now uses a more special and faster syscall/sysenter instruction.
Our task scheduling system will reflect this design: instead of simply passing the scheduler to the task (so long as it allows it to do whatever it wants), we will communicate with the system calls by passing information to the yield expression. Here, yield is an interruption, and it is the method of passing information to the scheduler (and from the scheduler).
To illustrate the system call, I'll make a small package of callable system calls:
Copy the Code code as follows:
Class Systemcall {
protected $callback;
Public function __construct (callable $callback) {
$this->callback = $callback;
}
Public Function __invoke (Task $task, Scheduler $scheduler) {
$callback = $this->callback; Can ' t call it directly in PHP:/
Return $callback ($task, $scheduler);
}
}
It will run like any other callable (using _invoke), but it requires the scheduler to pass the calling task and itself to the function. To solve this problem we have to slightly modify the scheduler's Run method:
Copy the Code code as follows:
Public Function run () {
while (! $this->taskqueue->isempty ()) {
$task = $this->taskqueue->dequeue ();
$retval = $task->run ();
if ($retval instanceof Systemcall) {
$retval ($task, $this);
Continue
}
if ($task->isfinished ()) {
unset ($this->taskmap[$task->gettaskid ()]);
} else {
$this->schedule ($task);
}
}
}
The first system call did nothing except return the task ID:
Copy the Code code as follows:
function GetTaskID () {
return new Systemcall (function (Task $task, Scheduler $scheduler) {
$task->setsendvalue ($task->gettaskid ());
$scheduler->schedule ($task);
});
}
This function does set the task ID to the next send value and dispatches the task again. Because a system call is used, the scheduler cannot invoke the task automatically, and we need to schedule the task manually (you'll see why later). To use this new system call, we'll rewrite the previous example:
Copy the Code code as follows:
function Task ($max) {
$tid = (yield gettaskid ()); <--here ' s the syscall!
for ($i = 1; $i <= $max; + + $i) {
echo "This is task $tid iteration $i. \ n";
Yield
}
}
$scheduler = new Scheduler;
$scheduler->newtask (Task (10));
$scheduler->newtask (Task (5));
$scheduler->run ();
This code will give the same output as the previous example. Note that system calls run as normal as any other call, but yield is increased in advance. To create new tasks and then kill them, you need more than two system calls:
Copy the Code code as follows:
function NewTask (Generator $coroutine) {
return new Systemcall (
function (Task $task, Scheduler $scheduler) use ($coroutine) {
$task->setsendvalue ($scheduler->newtask ($coroutine));
$scheduler->schedule ($task);
}
);
}
function Killtask ($tid) {
return new Systemcall (
function (Task $task, Scheduler $scheduler) use ($tid) {
$task->setsendvalue ($scheduler->killtask ($tid));
$scheduler->schedule ($task);
}
);
}
The Killtask function needs to add a method to the scheduler:
Copy the Code code as follows:
Public Function Killtask ($tid) {
if (!isset ($this->taskmap[$tid])) {
return false;
}
unset ($this->taskmap[$tid]);
This is a bit ugly and could are optimized so it does not having to walk the queue,
But assuming that killing tasks are rather rare I won ' t bother with it now
foreach ($this->taskqueue as $i = + $task) {
if ($task->gettaskid () = = = $tid) {
unset ($this->taskqueue[$i]);
Break
}
}
return true;
}
http://www.bkjia.com/PHPjc/328024.html www.bkjia.com true http://www.bkjia.com/PHPjc/328024.html techarticle PHP5.5 A better new feature is the implementation of support for generators and co-programs. For generators, PHP documentation and a variety of other blog posts (like this one or this one) already have ...