本篇文章给大家带来的内容是关于php为什么需要异步编程?php异步编程的详解(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

我对 php 异步的知识还比较混乱,写这篇是为了整理,可能有错。

传统的 php-fpm 一个进程执行一个请求,要达到多少并发,就要生成多少个进程。更糟糕的是每次请求都需要重新编译执行,导致并发一直上不来。因此出现了 Swoole 和 WorkerMan 两个国内流行的常驻内存框架[1]。这两个框架原理都是通过事件循环,让程序一直停留在内存,等待外部请求,达到高并发。

为什么需要异步

先来看一个例子

在工作目录下新建文件 slowServer.php

1

2

3

<?php

sleep(5); // 5秒后才能返回请求

echo 'done';

开启服务

1

$ php -S localhost:8081 slowServer.php

开另一个终端,安装依赖

1

2

3

$ pecl install event # 安装 event 扩展

$ composer require workerman/workerman

$ composer require react/http-client:^0.5.9

新建文件 worker.php

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

require_once __DIR__ . '/vendor/autoload.php';

use Workerman\Worker;

use Workerman\Connection\AsyncTcpConnection;

use Amp\Artax\Response;

$http_worker = new Worker("http://0.0.0.0:8082");

$http_worker->count = 1; // 只开一个进程

$http_worker->onMessage = function($connection, $host) {

    echo 1;

    $data = file_get_contents('http://localhost:8081');

    $connection->send($data);

};

Worker::runAll();

开启服务器

1

php worker.php start

在浏览器开启两个标签,都打开网址 http://localhost:8082 。这时可以看到终端输出“1”,过了一会儿又输出“1”,原因是8081服务器在处理第一个请求的时候阻塞在了等待8081返回之中,等第一个请求结束后,才开始处理第二个请求。也就是说请求是一个一个执行的,要达到多少个并发,就要建立多少个进程,跟 php-fpm 一样。现在修改一下代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

$http_worker->onMessage = function($connection, $host) {

    echo 1;

    $loop    = Worker::getEventLoop();

    $client  = new \React\HttpClient\Client($loop);

    $request = $client->request('GET', 'http://localhost:8081');

    $request->on('error', function(Exception $e) use ($connection) {

        $connection->send($e);

    });

    $request->on('response', function ($response) use ($connection) {

        $response->on('data', function ($data) use ($connection) {

            $connection->send($data);

        });

    });

    $request->end();

};

现在打开服务,再在浏览器发起请求,发现第二个“1”在请求后就马上输出了,而这时第一个请求还没结束。这表明进程不再阻塞,并发量取决于 cpu 和 内存,而不是进程数。

为什么需要异步

通过上面的例子已经很明白了,reactphp 框架通过把 http 请求变成异步,让 onMessage 函数变成非阻塞,cpu 可以去处理下一个请求。即从 cpu 循环等待 8081 返回,变成了 epoll 等待。

异步的意义在于把 cpu 从 io 等待中解放出来,可以处理其他计算任务。 如果你想知道怎么用框架实现异步,看到这里就可以了。WorkerMan 配合 ReactPHP 或者自身的 AsyncTcpConnection 已经可以满足很多 io 请求异步化的需求。下面继续讨论这些框架是怎么做到异步的。

哪些地方应该被做成异步

通过上面的例子已经知道一旦执行到不需要 cpu,但是要等待 io 的时候,应该把 io 的过程做成异步。

实现事件循环

上面的例子是通过 reactphp 把 http 请求变成了异步,其实 WorkerMan 框架本身也是异步的,下面来看看 WorkerMan 是怎么使 onMessage 函数可以异步接受请求。先来新建下面这个文件 react.php

1

2

3

4

5

6

7

8

9

10

11

12

13

<?php

$context = stream_context_create();

$socket = stream_socket_server('tcp://0.0.0.0:8081', $errno, $errmsg, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,$context); // 注册一个 fd(file descriptor)

function react($socket){

    $new_socket = stream_socket_accept($socket, 0, $remote_address);

    echo 1;

}

$eventBase = new EventBase();

$event = new Event($eventBase, $socket, Event::READ | Event::PERSIST, 'react', $socket); // 注册一个事件,检测 fd 有没有写入内容

$event->add();

$eventBase->loop(); // 开始循环

开始执行

1

$ php react.php

在另一个终端执行

1

telnet 127.0.0.1 8081

这时就会看到第一个终端输出'1'。

我之前写过一篇文章《php使用epoll》,是这篇文章的基础。那篇文章里事件回调是通过定时来实现,即

1

$event->add($seconds);

而这里,事件回调是通过检测 fd 是否有写入内容来实现,这个过程不需要 cpu 参与。当 fd 有内容写入时,会调函数 'react',这时开始使用 cpu。如果这时候进程执行另一个异步请求,比如用 reactphp 框架请求一个网页,那么程序会让出 cpu,此时如果有另一个请求进来,就可以回调执行另一个 'react' 函数。由此提高了并发量。

协程

生成器 Generater

这是生成器的 PHP 官方文档 http://php.net/manual/zh/lang...

1

2

3

4

5

6

7

8

9

10

11

12

<?php

function gen_one_to_three() {

    for ($i = 1; $i <= 3; $i++) {

        //注意变量$i的值在不同的yield之间是保持传递的。

        yield $i;

    }

}

$generator = gen_one_to_three();

foreach ($generator as $value) {

    echo "$value\n";

}

生成器就是每次程序执行到 yield 的时候保存状态,然后返回 $i,是否继续执行 gen_one_to_three 里的循环,取决于主程序是否继续调用

什么是协程

上面的程序另一种写法是

1

2

3

4

5

6

7

8

9

10

11

12

<?php

$i = 1;

function gen_one_to_three() {

    global $i;

    if ($i<=3){

        return $i++;

    }

}

while ($value = gen_one_to_three()) {

    echo "$value\n";

}

由此可见,协程就是一种对函数的封装,使其变成一种可以被中断的函数,行为更像是子进程或子线程,而不是函数。协程的具体写法这里不细写,因为协程的写法十分复杂,可能需要再做一层封装才能好用。

协程与异步

既然协程可以被中断,那么只要在程序发起请求后发起事件循环,然后用 yield 返回,然后程序继续执行主程序部分,等事件返回后触发函数,执行 Generatot::next() 或 Generator::send() 来继续执行协程部分。封装好后就好像没有异步回调函数一样,和同步函数很像。

现在已经有 ampphp 和 swoole 两个框架封装了协程,有兴趣可以了解一下。

明确的学习思路能更高效的学习

点击加入该群学习

php为什么需要异步编程?php异步编程的详解(附示例)的更多相关文章

  1. Java多线程编程中Future模式的详解

    Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...

  2. Java多线程编程中Future模式的详解<转>

    Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...

  3. Python编程之列表操作实例详解【创建、使用、更新、删除】

    Python编程之列表操作实例详解[创建.使用.更新.删除] 这篇文章主要介绍了Python编程之列表操作,结合实例形式分析了Python列表的创建.使用.更新.删除等实现方法与相关操作技巧,需要的朋 ...

  4. VMware 虚拟化编程(7) — VixDiskLib 虚拟磁盘库详解之三

    目录 目录 前文列表 VixDiskLib 虚拟磁盘库 VixDiskLib_GetMetadataKeys VixDiskLib_ReadMetadata 获取虚拟磁盘元数据 VixDiskLib_ ...

  5. VMware 虚拟化编程(6) — VixDiskLib 虚拟磁盘库详解之二

    目录 目录 前文列表 VixDiskLib 虚拟磁盘库 VixDiskLib_Open 打开 VMDK File VixDiskLib_Read 读取 VMDK File 数据 VixDiskLib_ ...

  6. VMware 虚拟化编程(5) — VixDiskLib 虚拟磁盘库详解之一

    目录 目录 前文列表 VixDiskLib 虚拟磁盘库 虚拟磁盘数据的传输方式 Transport Methods VixDiskLib_ListTransportModes 枚举支持的传输模式 Vi ...

  7. Java程序设计(2021春)——第一章课后题(选择题+编程题)答案与详解

    Java程序设计(2021春)--第一章课后题(选择题+编程题)答案与详解 目录 Java程序设计(2021春)--第一章课后题(选择题+编程题)答案与详解 第一章选择题 1.1 Java与面向对象程 ...

  8. Java程序设计(2021春)——第二章课后题(选择题+编程题)答案与详解

    Java程序设计(2021春)--第二章课后题(选择题+编程题)答案与详解 目录 Java程序设计(2021春)--第二章课后题(选择题+编程题)答案与详解 第二章选择题 2.1 面向对象方法的特性 ...

  9. Java程序设计(2021春)——第四章接口与多态课后题(选择题+编程题)答案与详解

    Java程序设计(2021春)--第四章接口与多态课后题(选择题+编程题)答案与详解 目录 Java程序设计(2021春)--第四章接口与多态课后题(选择题+编程题)答案与详解 第四章选择题 4.0 ...

  10. python异步并发模块concurrent.futures入门详解

    concurrent.futures是一个非常简单易用的库,主要用来实现多线程和多进程的异步并发. 本文主要对concurrent.futures库相关模块进行详解,并分别提供了详细的示例demo. ...

随机推荐

  1. 阿里巴巴开源故障注入工具_chaosblade

    chaosblade是阿里巴巴最近开源的一款故障注入的工具,因为我最近在做公司的虚拟化平台的可靠性测试工具,无意中发现这个工具,个人感觉比较有用,用起来也比较简单,所以拿出来分享一下,期望对大家的工作 ...

  2. Activity 学习(二) 搭建第一个Activity流程框架

    本次示例使用的IDER测试完成 测试背景 : xx饿了去饭店吃饭  需要先和服务员点餐  点完餐后服务员将菜品传递给厨师制作  制作完成后吃饱 一 :创建流程图 创建上一篇测试成功出现的BpmnFil ...

  3. python中根据时间获取周数,通过周数获取时间

    # 时间## 时间和周数 import time import datetime # 获取今天是第几周 print(time.strftime('%W')) # 获取当前是周几(0-6,0代表周一) ...

  4. Leetcode Tags(8)Binary Search

    一.475. Heaters 输入: [1,2,3],[2] 输出: 1 解释: 仅在位置2上有一个供暖器.如果我们将加热半径设为1,那么所有房屋就都能得到供暖. 输入: [1,2,3,4],[1,4 ...

  5. Java基础(十五)异常(Exception)

    1.处理错误的要求 如果由于出现错误而使得某些操作没有完成,程序应该: 返回到一种安全状态,并能够让用户执行一些其他的命令. 允许用户保存所有操作的结果,并以妥善的方式终止程序. 2.程序中可能出现的 ...

  6. 实现文字色彩渐变(Mask)

    文字色彩渐变是指的文字本身的颜色,不是背景渐变.要实现此效果可以采用Mask组件,本文先从介绍mask说起 1)Mask介绍 mask组件实现的作用是,mask组件所在游戏物体下的子游戏物体在mask ...

  7. SpringBoot系列之YAML配置用法

    1.全局配置 SpringBoot的全局配置文件有两种: application.properties application.yml 配置文件的作用:修改SpringBoot自动配置的默认值,主要是 ...

  8. URL中文参数,JSON转换,PHP赋值JS

    var jsonProps = { "dispMode":dispMode, "autoRun":autoRun, "clientPath" ...

  9. [考试反思]0919csp-s模拟测试47:苦难

    ISOLATION 也不粘上面的了,先管好自己. 附了个近期总分,可以看出什么. 反思一下考试心态: 开场看题目,T1傻逼题不用脑子,T2傻逼板子,T3... 这T3是啥啊?没看懂题目啊?再看一遍.啥 ...

  10. 最新JetBrains PyCharm 使用教程--创建或导入项目(二)

    Python简介 Python是一种非常流行的开源编程语言.得益于无尽的模块选项,Python今天广泛用于脚本语言.Web开发.移动和桌面在许多领域.随着人工智能的复兴,数据科学的崛起,Python更 ...