swoole with php Coroutine asynchronous non-blocking IO Development

"Coroutine can be interrupted in the face of obstruction of transferring resources to the initiative, the scheduler to select another coroutine run. In order to achieve non-blocking IO"
But php does not support native coroutine, if not handed over asynchronous processes to execute the face of obstruction is no sense, or synchronous execution of code, as follows:
function foo()
{
    $db=new Db();
    $result=(yield $db->query());
    yield $result;
}
The above database query operations are blocked, this step is found to perform a blocking operation when the scheduler to schedule the coroutine, then the scheduler how to do? Select the remaining coroutine execution? That the co-operation should the process of blocking when to perform, who handed over to enforce it? So put aside the non-blocking IO belong to an asynchronous call to talk about bullying in php Association process.
The swoole asynchronous task to provide a solution to implement asynchronous, can refer to the official documentation on swoole_task
Core functions and
 
The first request to form a coroutine
 
First create a swoole_server and set callback
class HttpServer implements Server
{
    private $swooleHttpServer;
 
    public function __construct(\swoole_http_server $swooleHttpServer)
    {
        $this->swooleHttpServer = $swooleHttpServer;
    }
 
    public function start()
    {
        $this->swooleHttpServer->on('start', [$this, 'onStart']);
        $this->swooleHttpServer->on('shutdown', [$this, 'onShutdown']);
 
        $this->swooleHttpServer->on('workerStart', [$this, 'onWorkerStart']);
        $this->swooleHttpServer->on('workerStop', [$this, 'onWorkerStop']);
        $this->swooleHttpServer->on('workerError', [$this, 'onWorkerError']);
        $this->swooleHttpServer->on('task', [$this, 'onTask']);
        $this->swooleHttpServer->on('finish', [$this, 'onFinish']);
 
 
        $this->swooleHttpServer->on('request', [$this, 'onRequest']);
 
        $this->swooleHttpServer->start();
    }
onRequest method:
 public function onRequest(\swoole_http_request $request, \swoole_http_response $response)
    {
        $requestHandler = new RequestHandler($request, $response);
        $requestHandler->handle();
    }
A method performed ReqeustHandler the handle, to parse the routing request, and created a controller, call the appropriate methods, with
 public function handle()
    {
        $this->context = new Context($this->request, $this->response, $this->getFd());
        $this->router = new Router($this->request);
 
        try {
            if (false === $this->router->parse()) {
                $this->response->output('');
                return;
            }
            $coroutine = $this->doRun();
            $task = new Task($coroutine, $this->context);
            $task->run();
        } catch (\Exception $e) {
            PcsExceptionHandler::handle($e, $this->response);
        }
    }
    
 private function doRun()
    {
        $ret = (yield $this->dispatch());
        yield $this->response->send($ret);
    }
The above code is the result of calling ret action () is, yield $ this-> response-> send ($ ret); is the response to client requests.
This time is the coroutine $ request a coroutine (Genetator object) is formed, comprising the entire flow of the request, the next step is to perform scheduling coroutine execution result to obtain real.
 
Scheduling coroutine
 
namespace Pcs\Coroutine;
 
use Pcs\Network\Context\Context;
 
class Task
{
    private $coroutine;
    private $context;
    private $status;
    private $scheduler;
    private $sendValue;
 
    public function __construct(\Generator $coroutine, Context $context)
    {
        $this->coroutine = $coroutine;
        $this->context = $context;
        $this->scheduler = new Scheduler($this);
 
    }
 
    public function run()
    {
        while (true) {
            try {
                $this->status = $this->scheduler->schedule();
                switch ($this->status) {
                    case TaskStatus::TASK_WAIT:
                        echo "task status: TASK_WAIT\n";
                        return null;
 
                    case TaskStatus::TASK_DONE:
                        echo "task status: TASK_DONE\n";
                        return null;
 
                    case TaskStatus::TASK_CONTINUE;
                        echo "task status: TASK_CONTINUE\n";
                        break;
                }
 
            } catch (\Exception $e) {
                $this->scheduler->throwException($e);
            }
        }
    }
    public function setCoroutine($coroutine)
    {
        $this->coroutine = $coroutine;
    }
 
    public function getCoroutine()
    {
        return $this->coroutine;
    }
 
    public function valid()
    {
        if ($this->coroutine->valid()) {
            return true;
        } else {
            return false;
        }
    }
 
    public function send($value)
    {
        $this->sendValue = $value;
        $ret = $this->coroutine->send($value);
        return $ right;
    }
 
    public function getSendVal()
    {
        return $this->sendValue;
    }
}
Task Generator objects depends on the coroutine $, defines some get / set methods, and in some ways Generator Task class, Task :: run () method is used to perform scheduling of the coroutine is executed by the Schedule scheduling behavior, each time schedule will return to the current state of the scheduling. A plurality of co-routines share the scheduler, and the run method here creates a coroutine scheduler for each, because each coroutine is a client's request, using a separate scheduler to reduce the mutual influence, and scheduling order between a plurality of co-routines are handled swoole, scheduler without concern here. Scheduled code is given below:
namespace Pcs\Coroutine;
 
class Scheduler
{
    private $task;
    private $stack;
    const SCHEDULE_CONTINUE = 10;
 
    public function __construct(Task $task)
    {
        $this->task = $task;
        $this->stack = new \SplStack();
    }
    
    public function schedule()
    {
        $coroutine = $this->task->getCoroutine();
        $value = $coroutine->current();
 
        $status = $this->handleSystemCall($value);
        if ($status !== self::SCHEDULE_CONTINUE) return $status;
 
        $status = $this->handleStackPush($value);
        if ($status !== self::SCHEDULE_CONTINUE) return $status;
 
        $status = $this->handleAsyncJob($value);
        if ($status !== self::SCHEDULE_CONTINUE) return $status;
 
        $status = $this->handelYieldValue($value);
        if ($status !== self::SCHEDULE_CONTINUE) return $status;
 
        $status = $this->handelStackPop();
        if ($status !== self::SCHEDULE_CONTINUE) return $status;
 
 
        return TaskStatus::TASK_DONE;
    }
 
    public function isStackEmpty()
    {
        return $this->stack->isEmpty();
    }
 
    private function handleSystemCall($value)
    {
        if (!$value instanceof SystemCall) {
            return self::SCHEDULE_CONTINUE;
        }
    }
 
    private function handleStackPush($value)
    {
        if (!$value instanceof \Generator) {
            return self::SCHEDULE_CONTINUE;
        }
 
        $coroutine = $this->task->getCoroutine();
        $this->stack->push($coroutine);
        $this->task->setCoroutine($value);
 
        return TaskStatus::TASK_CONTINUE;
    }
 
    private function handleAsyncJob($value)
    {
        if (!is_subclass_of($value, Async::class)) {
            return self::SCHEDULE_CONTINUE;
        }
 
        $value->execute([$this, 'asyncCallback']);
 
        return TaskStatus::TASK_WAIT;
    }
 
    public function asyncCallback($response, $exception = null)
    {
        if ($exception !== null
            && $exception instanceof \Exception
        ) {
            $this->throwException($exception, true);
        } else {
            $this->task->send($response);
            $this->task->run();
        }
    }
 
    private function handelYieldValue($value)
    {
        if (!$this->task->valid()) {
            return self::SCHEDULE_CONTINUE;
        }
 
        $ret = $this->task->send($value);
        return TaskStatus::TASK_CONTINUE;
    }
 
 
    private function handelStackPop()
    {
        if ($this->isStackEmpty()) {
            return self::SCHEDULE_CONTINUE;
        }
 
        $coroutine = $this->stack->pop();
        $this->task->setCoroutine($coroutine);
 
        $value = $this->task->getSendVal();
        $this->task->send($value);
 
        return TaskStatus::TASK_CONTINUE;
    }
 
    public function throwException($e, $isFirstCall = false)
    {
        if ($this->isStackEmpty()) {
            $this->task->getCoroutine()->throw($e);
            return;
        }
 
        try {
            if ($isFirstCall) {
                $coroutine = $this->task->getCoroutine();
            } else {
                $coroutine = $this->stack->pop();
            }
 
            $this->task->setCoroutine($coroutine);
            $coroutine->throw($e);
 
            $this->task->run();
        } catch (\Exception $e) {
            $this->throwException($e);
        }
    }
}
The method may schedule the Scheduler Task obtain the current coroutine, and () method returns the current value in the breakpoint by Current, and then turn call five methods to process the return value.
1: shopping system call
If the return value is an object SystemCall type, the system call is executed, such as operating killTask, systemCall is the first priority.
2: shop stack push
A function call in the function B, then B A function is called a function of subroutines (subroutine), but it can not function as normal call as coroutine.
function funcA()
{
    return funcB();
}
 
function genA()
{
    yield genB();
}
In the funcA funcB (); funcB returns an execution result, but in the genA, yield genB (); returns a Generator object rather than the final execution result of Genb. Want genB execution results need to be scheduled for genB, but he also might have genB genC () genD () coroutine nested, so in order to make coroutine like a normal function call, where the use coroutine stack to achieve.
image

As shown above, when the scheduler acquires GENA (parent coroutine) when the return value is instance of Generator, parent scheduler coroutine will push the stack, and then allocated to the sub coroutine Task, continue scheduling sub coroutine. And so forth until the last sub coroutine returned, then start pop, the stack is successively removed coroutine
 
3:handleAsyncJob
handleAsyncJob is the core coroutine scheduling
private function handleAsyncJob($value)
    {
        if (!is_subclass_of($value, Async::class)) {
            return self::SCHEDULE_CONTINUE;
        }
 
        $value->execute([$this, 'asyncCallback']);
 
        return TaskStatus::TASK_WAIT;
    }
 
    public function asyncCallback($response, $exception = null)
    {
        if ($exception !== null
            && $exception instanceof \Exception
        ) {
            $this->throwException($exception, true);
        } else {
            $this->task->send($response);
            $this->task->run();
        }
    }
If the return value is scheduled coroutine Async inherited subclass or instance Asycn implements the interface when the Async execute method executes. Here, for example with a database query mysqli class.
    public function execute(callable $callback)
    {
        $this->callback = $callback;
        $serv = ServerHolder::getServer();
        $serv->task($this->sql, -1, [$this, 'queryReady']);
 
    }
 
    public function queryReady(\swoole_http_server $serv, $task_id, $data)
    {
        $queryResult = unserialize($data);
        $exception = null;
        if ($queryResult->errno != 0) {
 
            $exception = new \Exception($queryResult->error);
        }
        call_user_func_array($this->callback, [$queryResult, $exception]);
    }
 
The execute method as a callback function after the asynchronous operation is complete, the execute method Mysqli class, start an asynchronous swoole_task, the operation to sql swoole_task executed asynchronously, executes after execution queryReady method $ this- performed asynchronously return data after parsing> callback () is passed before the scheduler asyncCallback, the method will be performed after the abnormality detection in the send () method sends the result of the asynchronous execution interrupted to continue carried out.
handleAsyncJob does not wait for an asynchronous operation returns the result, but the direct TASK_WAIT return signal back to the top of Task-> run () method may be seen TASK_WAIT signal causes run () method returns null, release the current worker, the scheduling flow diagram of the As shown in FIG,
image

4:handleYieldValue
private function handelYieldValue($value)
    {
        if (!$this->task->valid()) {
            return self::SCHEDULE_CONTINUE;
        }
 
        $ret = $this->task->send($value);
        return TaskStatus::TASK_CONTINUE;
    }
 
If a particular yield the return value is neither asynchronous call nor Generator, it is judged whether the current generator is valid (whether completed) If finished, continue scheduling, perform the following handleStackPush method, otherwise return Task_Continue continue scheduling, that is, He said that many times in a generator yield, the return value will ultimately take the final yield.
5: shopping stack push
When the previous step is determined! $ This-> task-> valid () before the current time is finished of the generator, performs the method for controlling the coroutine stack pop operation, first check Stac non-null, non-empty if a parent pop coroutine, and returns the current value of the coroutine send () to interrupt the parent coroutine execution continues.
Where coroutine advantage
When first encountered IO request, the synchronous operation will cause the current request block waiting for IO returns, reflected in the swoole that a request has been occupied by a worker at the IO.
image

But when using a coroutine scheduling, where users can manually blocked interrupted by yield, referred to swoole_task asynchronous operation, while releasing worker occupation to handle other requests.
When asynchronous processing executed after the end of the schedule continues.
image

Note php coroutine only responsible interrupted asynchronous operation is done Swoole_task

Guess you like

Origin www.cnblogs.com/winner192/p/11704147.html