首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
182 阅读
2
php接口优化 使用curl_multi_init批量请求
144 阅读
3
《从菜鸟到大师之路 ElasticSearch 篇》
107 阅读
4
2024年备考系统架构设计师
104 阅读
5
PHP 文件I/O
92 阅读
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
登录
Search
标签搜索
php函数
php语法
性能优化
安全
错误和异常处理
问题
vue
Composer
Session
缓存
框架
Swoole
api
并发
异步
正则表达式
php-fpm
mysql 索引
开发规范
协程
dafenqi
累计撰写
786
篇文章
累计收到
28
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
100
篇与
的结果
2023-08-10
Swoole进程结构
Swoole进程结构一、进程的基本知识什么是进程,所谓进程其实就是操作系统中一个正在运行的程序,我们在一个终端当中,通过php,运行一个php文件,这个时候就相当于我们创建了一个进程,这个进程会在系统中驻存,申请属于它自己的内存空间系统资源并且运行相应的程序对于一个进程来说,它的核心内容分为两个部分,一个是它的内存,这个内存是这进程创建之初从系统分配的,它所有创建的变量都会存储在这一片内存环境当中一个是它的上下文环境我们知道进程是运行在操作系统的,那么对于程序来说,它的运行依赖操作系统分配给它的资源,操作系统的一些状态。在操作系统中可以运行多个进程的,对于一个进程来说,它可以创建自己的子进程,那么当我们在一个进程中创建出若干个子进程的时候那么可以看到如图,子进程和父进程一样,拥有自己的内存空间和上下文环境clipboard.png二、Swoole进程结构Swoole的高效不仅仅于底层使用c编写,他的进程结构模型也使其可以高效的处理业务,我们想要深入学习,并且在实际的场景当中使用必须了解,下面我们先看一下结构图:clipboard.png首先先介绍下swoole的这几种进程分别是干什么的:从这些层级的名字,我们先大概说一下,下面这些层级分别是干什么的,做一个详细的说明。Master进程:主进程Manger进程:管理进程Worker进程:工作进程Task进程:异步任务工作进程1、Master进程第一层,Master进程,这个是swoole的主进程,这个进程是用于处理swoole的核心事件驱动的,那么在这个进程当中可以看到它拥有一个MainReactor[线程]以及若干个Reactor[线程],swoole所有对于事件的监听都会在这些线程中实现,比如来自客户端的连接,信号处理等。clipboard.png每一个线程都有自己的用途,下面多每个线程有一个了解MainReactor(主线程)主线程会负责监听server socket,如果有新的连接accept,主线程会评估每个Reactor线程的连接数量。将此连接分配给连接数最少的reactor线程,做一个负载均衡。Reactor线程组Reactor线程负责维护客户端机器的TCP连接、处理网络IO、收发数据完全是异步非阻塞的模式。swoole的主线程在Accept新的连接后,会将这个连接分配给一个固定的Reactor线程,在socket可读时读取数据,并进行协议解析,将请求投递到Worker进程。在socket可写时将数据发送给TCP客户端。心跳包检测线程(HeartbeatCheck)Swoole配置了心跳检测之后,心跳包线程会在固定时间内对所有之前在线的连接发送检测数据包UDP收包线程(UdpRecv)接收并且处理客户端udp数据包2、管理进程ManagerSwoole想要实现最好的性能必须创建出多个工作进程帮助处理任务,但Worker进程就必须fork操作,但是fork操作是不安全的,如果没有管理会出现很多的僵尸进程,进而影响服务器性能,同时worker进程被误杀或者由于程序的原因会异常退出,为了保证服务的稳定性,需要重新创建worker进程。Swoole在运行中会创建一个单独的管理进程,所有的worker进程和task进程都是从管理进程Fork出来的。管理进程会监视所有子进程的退出事件,当worker进程发生致命错误或者运行生命周期结束时,管理进程会回收此进程,并创建新的进程。换句话也就是说,对于worker、task进程的创建、回收等操作全权有“保姆”Manager进程进行管理。再来一张图梳理下Manager进程和Worker/Task进程的关系。clipboard.png3、Worker进程worker 进程属于swoole的主逻辑进程,用户处理客户端的一系列请求,接受由Reactor线程投递的请求数据包,并执行PHP回调函数处理数据生成响应数据并发给Reactor线程,由Reactor线程发送给TCP客户端可以是异步非阻塞模式,也可以是同步阻塞模式4、Task进程taskWorker进程这一进城是swoole提供的异步工作进程,这些进程主要用于处理一些耗时较长的同步任务,在worker进程当中投递过来。三、进程查看及流程梳理当启动一个Swoole应用时,一共会创建2 + n + m个进程,2为一个Master进程和一个Manager进程,其中n为Worker进程数。m为TaskWorker进程数。默认如果不设置,swoole底层会根据当前机器有多少CPU核数,启动对应数量的Reactor线程和Worker进程。我机器为1核的。Worker为1。所以现在默认我启动了1个Master进程,1个Manager进程,和1个worker进程,TaskWorker没有设置也就是为0,当前server会产生3个进程。在启动了server之后,在命令行查看当前产生的进程clipboard.png这三个进程中,所有进程的根进程,也就是例子中的2123进程,就是所谓的Master进程;而2212进程,则是Manager进程;最后的2321进程,是Worker进程。client跟server的交互1、client请求到达 Main Reactor,Client实际上是与Master进程中的某个Reactor线程发生了连接。2、Main Reactor根据Reactor的情况,将请求注册给对应的Reactor (每个Reactor都有epoll。用来监听客户端的变化) 3、客户端有变化时Reactor将数据交给worker来处理4、worker处理完毕,通过进程间通信(比如管道、共享内存、消息队列)发给对应的reactor。 5、reactor将响应结果发给相应的连接请求处理完成示意图:clipboard.png后续准备本文是在自己学习Swoole接触到的一些知识,在初步整理后发送出来,希望能与大家一起学习,文章不足等问题大家可以一起讨论学习,欢迎骚扰~~。后面准备从网络模型入手更好的理解swoole的实现原理,比较与传统PHP-FPM工作模式的问题,之前出过一篇关于(一)如何实现一个单进程阻塞的网络服务器大家可以先了解下,如何一步步演变为多进程master-worker模型。
2023年08月10日
17 阅读
0 评论
0 点赞
2023-08-10
swoole 协程初体验
swoole 协程初体验date: 2018-5-30 14:31:38title: swoole| swoole 协程初体验description: 通过协程的执行初窥 swoole 中协程的调度; 理解协程为什么快; swoole 协程和 go 协程对比折腾 swoole 协程有一段时间了, 总结一篇入门贴, 希望对新手有帮助.内容概览:协程的执行顺序: 初窥 swoole 中协程的调度协程为什么快: 减少IO阻塞带来的性能损耗swoole 协程和 go 协程对比: 单进程 vs 多线程协程的执行顺序先来看看基础的例子:go(function () {echo "hello go1 \n";});echo "hello main \n";go(function () {echo "hello go2 \n";});go() 是 \Co::create() 的缩写, 用来创建一个协程, 接受 callback 作为参数, callback 中的代码, 会在这个新建的协程中执行.备注: \Swoole\Coroutine 可以简写为 \Co上面的代码执行结果:root@b98940b00a9b /v/w/c/p/swoole# php co.phphello go1hello mainhello go2执行结果和我们平时写代码的顺序, 好像没啥区别. 实际执行过程:运行此段代码, 系统启动一个新进程遇到 go(), 当前进程中生成一个协程, 协程中输出 heelo go1, 协程退出进程继续向下执行代码, 输出 hello main再生成一个协程, 协程中输出 heelo go2, 协程退出运行此段代码, 系统启动一个新进程. 如果不理解这句话, 你可以使用如下代码:// co.php<?phpsleep(100);执行并使用 ps aux 查看系统中的进程:root@b98940b00a9b /v/w/c/p/swoole# php co.php &⏎root@b98940b00a9b /v/w/c/p/swoole# ps auxPID USER TIME COMMAND1 root 0:00 php -a10 root 0:00 sh 19 root 0:01 fish 749 root 0:00 php co.php 760 root 0:00 ps aux⏎我们来稍微改一改, 体验协程的调度:use Co;go(function () {Co::sleep(1); // 只新增了一行代码 echo "hello go1 \n";});echo "hello main \n";go(function () {echo "hello go2 \n";});\Co::sleep() 函数功能和 sleep() 差不多, 但是它模拟的是 IO等待(IO后面会细讲). 执行的结果如下:root@b98940b00a9b /v/w/c/p/swoole# php co.phphello mainhello go2hello go1怎么不是顺序执行的呢? 实际执行过程:运行此段代码, 系统启动一个新进程遇到 go(), 当前进程中生成一个协程协程中遇到 IO阻塞 (这里是 Co::sleep() 模拟出的 IO等待), 协程让出控制, 进入协程调度队列进程继续向下执行, 输出 hello main执行下一个协程, 输出 hello go2之前的协程准备就绪, 继续执行, 输出 hello go1到这里, 已经可以看到 swoole 中 协程与进程的关系, 以及 协程的调度, 我们再改一改刚才的程序:go(function () {Co::sleep(1); echo "hello go1 \n";});echo "hello main \n";go(function () {Co::sleep(1); echo "hello go2 \n";});我想你已经知道输出是什么样子了:root@b98940b00a9b /v/w/c/p/swoole# php co.phphello mainhello go1hello go2⏎协程快在哪? 减少IO阻塞导致的性能损失大家可能听到使用协程的最多的理由, 可能就是 协程快. 那看起来和平时写得差不多的代码, 为什么就要快一些呢? 一个常见的理由是, 可以创建很多个协程来执行任务, 所以快. 这种说法是对的, 不过还停留在表面.首先, 一般的计算机任务分为 2 种:CPU密集型, 比如加减乘除等科学计算IO 密集型, 比如网络请求, 文件读写等其次, 高性能相关的 2 个概念:并行: 同一个时刻, 同一个 CPU 只能执行同一个任务, 要同时执行多个任务, 就需要有多个 CPU 才行并发: 由于 CPU 切换任务非常快, 快到人类可以感知的极限, 就会有很多任务 同时执行 的错觉了解了这些, 我们再来看协程, 协程适合的是 IO 密集型 应用, 因为协程在 IO阻塞 时会自动调度, 减少IO阻塞导致的时间损失.我们可以对比下面三段代码:普通版: 执行 4 个任务$n = 4;for ($i = 0; $i < $n; $i++) {sleep(1); echo microtime(true) . ": hello $i \n";};echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.php1528965075.4608: hello 01528965076.461: hello 11528965077.4613: hello 21528965078.4616: hello 3hello mainreal 0m 4.02suser 0m 0.01ssys 0m 0.00s⏎单个协程版:$n = 4;go(function () use ($n) {for ($i = 0; $i < $n; $i++) { Co::sleep(1); echo microtime(true) . ": hello $i \n"; };});echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.phphello main1528965150.4834: hello 01528965151.4846: hello 11528965152.4859: hello 21528965153.4872: hello 3real 0m 4.03suser 0m 0.00ssys 0m 0.02s⏎多协程版: 见证奇迹的时刻$n = 4;for ($i = 0; $i < $n; $i++) {go(function () use ($i) { Co::sleep(1); echo microtime(true) . ": hello $i \n"; });};echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.phphello main1528965245.5491: hello 01528965245.5498: hello 31528965245.5502: hello 21528965245.5506: hello 1real 0m 1.02suser 0m 0.01ssys 0m 0.00s⏎为什么时间有这么大的差异呢:普通写法, 会遇到 IO阻塞 导致的性能损失单协程: 尽管 IO阻塞 引发了协程调度, 但当前只有一个协程, 调度之后还是执行当前协程多协程: 真正发挥出了协程的优势, 遇到 IO阻塞 时发生调度, IO就绪时恢复运行我们将多协程版稍微修改一下:多协程版2: CPU密集型$n = 4;for ($i = 0; $i < $n; $i++) {go(function () use ($i) { // Co::sleep(1); sleep(1); echo microtime(true) . ": hello $i \n"; });};echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.php1528965743.4327: hello 01528965744.4331: hello 11528965745.4337: hello 21528965746.4342: hello 3hello mainreal 0m 4.02suser 0m 0.01ssys 0m 0.00s⏎只是将 Co::sleep() 改成了 sleep(), 时间又和普通版差不多了. 因为:sleep() 可以看做是 CPU密集型任务, 不会引起协程的调度Co::sleep() 模拟的是 IO密集型任务, 会引发协程的调度这也是为什么, 协程适合 IO密集型 的应用.再来一组对比的例子: 使用 redis// 同步版, redis使用时会有 IO 阻塞$cnt = 2000;for ($i = 0; $i < $cnt; $i++) {$redis = new \Redis(); $redis->connect('redis'); $redis->auth('123'); $key = $redis->get('key');}// 单协程版: 只有一个协程, 并没有使用到协程调度减少 IO 阻塞go(function () use ($cnt) {for ($i = 0; $i < $cnt; $i++) { $redis = new Co\Redis(); $redis->connect('redis', 6379); $redis->auth('123'); $redis->get('key'); }});// 多协程版, 真正使用到协程调度带来的 IO 阻塞时的调度for ($i = 0; $i < $cnt; $i++) {go(function () { $redis = new Co\Redis(); $redis->connect('redis', 6379); $redis->auth('123'); $redis->get('key'); });}性能对比:多协程版root@0124f915c976 /v/w/c/p/swoole# time php co.phpreal 0m 0.54suser 0m 0.04ssys 0m 0.23s⏎同步版root@0124f915c976 /v/w/c/p/swoole# time php co.phpreal 0m 1.48suser 0m 0.17ssys 0m 0.57s⏎swoole 协程和 go 协程对比: 单进程 vs 多线程接触过 go 协程的 coder, 初始接触 swoole 的协程会有点 懵, 比如对比下面的代码:package mainimport ("fmt" "time")func main() {go func() { fmt.Println("hello go") }() fmt.Println("hello main") time.Sleep(time.Second)}14:11 src $ go run test.gohello mainhello go刚写 go 协程的 coder, 在写这个代码的时候会被告知不要忘了 time.Sleep(time.Second), 否则看不到输出 hello go, 其次, hello go 与 hello main 的顺序也和 swoole 中的协程不一样.原因就在于 swoole 和 go 中, 实现协程调度的模型不同.上面 go 代码的执行过程:运行 go 代码, 系统启动一个新进程查找 package main, 然后执行其中的 func mian()遇到协程, 交给协程调度器执行继续向下执行, 输出 hello main如果不添加 time.Sleep(time.Second), main 函数执行完, 程序结束, 进程退出, 导致调度中的协程也终止go 中的协程, 使用的 MPG 模型:M 指的是 Machine, 一个M直接关联了一个内核线程P 指的是 processor, 代表了M所需的上下文环境, 也是处理用户级代码逻辑的处理器G 指的是 Goroutine, 其实本质上也是一种轻量级的线程MPG 模型而 swoole 中的协程调度使用 单进程模型, 所有协程都是在当前进程中进行调度, 单进程的好处也很明显 -- 简单 / 不用加锁 / 性能也高.无论是 go 的 MPG模型, 还是 swoole 的 单进程模型, 都是对 CSP理论 的实现.CSP通信方式, 在1985年时的论文就已经有了, 做理论研究的人, 如果没有能提前几年, 十几年甚至几十年的大胆假设, 可能很难提高了.写在最后今天从 go() 出发, 得以一瞥协程世界, 协程的世界里还有很多很有意思的东西, 需要我们去发现. 比如:我们普通版的代码是当前进程里执行的, 只是单个进程, 可我们现在可能有了很多协程, 会不会有什么奇遇呢?还有一个细节: swoole 中有 Co::sleep() 和 sleep() 2个方法的, 而 go 中只有 time.Sleep() 一个方法?这是 swoole 协程需要经历的一个阶段(毕竟 go 快 10 年了), 还不够 智能的判断 IO阻塞, 所以上面也使用了相应的协程版 redis co\Redis() -- 你得使用配套协程版, 才能达到协程调度的效果.如果对协程的发展阶段感兴趣, 可以阅读下面这篇文章:Why c++ coroutine?Why libgo?: 关于协程全景式的概述的, 推荐花时间读一读想解锁 swoole 协程的更多姿势:Swoole 2.1 正式版发布,协程+通道带来全新的 PHP 编程模式Swoole 4.0 正式版,面向生产环境的 PHP 协程引擎Swoole4-全新的PHP编程模式_韩天峰_PHPCON2018最后, 本期示例代码可以从我的开源项目中获取, 请享用
2023年08月10日
7 阅读
0 评论
0 点赞
2023-08-10
如何少写PHP “烂”代码
如何少写PHP “烂”代码写给初生牛犊不怕虎的童鞋们,大佬可随意摘看本章基于PHP Laravel前言经常会有人问目录如何设计比较好?代码如何分布好?怎么写一个可维护的项目?“烂”项目我也没少写,以下是参考互联网各大佬的文章总结及个人开发经验而来.Controllerclipboard.pngController顾名思义是控制器,在入门PHP的时候,就知道Controller代表MVC中的C层,MVC本身的概念就代码分离,教你如何如何将业务分开,但面临着业务的不断发展,代码的复杂度也随之提高,功能与功能之间的链接错综复杂,最后你的MVC就变成了下图,所以仅仅依托MVC的设计思想已经无法支撑不断发展的业务。现在我们将Controller的任务和能力重新定义,控制器仅仅控制Http Reqeust的请求,这样就符合了SOLID 单一功能原则.clipboard.png直接将业务代码写在Controller中,会使得代码及其臃肿,不易于维护和扩展<?phpnamespace App\Http\Controller; class UserController extends Controller{ public function register(Request $request){ $user = new User(); $user->username = $request->input('username'); $user->password = $request->input('password'); $result = $user->save(); return $result; } }这时就应该思考如何分离业务代码,我们引入Service的概念ServiceService本身译为服务将外部方法,公共方法注入到Service将Service注入到控制器clipboard.png像上图这样UserController<?phpnamespace App\Http\Controller; class UserController extends Controller{ public $request; protected $userService; public function __construct(Request $request, UserService $userService) { $this->request = $request; $this->userService = $userService; } public function register() { //... validation return $this->userService->register ($this->request->all()); } }UserService<?phpnamespace App\Service; class UserService{ public function register($data) { $username = $data['username']; $password = $data['password']; $password = encrypt ($password); $user = new User(); $user->username = $username; $user->password = $password; $result = $user->save(); return $result; } }到现在为止,我们至少将业务与请求彻底分开了。但还是不如人意,如果把所有的业务及CURD全部写在Service中,那只不过是将Controller的臃肿转移到了Service,那Service就没有什么存在意义了。所以我们需要继续分割Service,将对数据库的R操作独立出来,因为CUD的操作基本是一贯不变的,而R操作根据业务的复杂度则变的多姿多彩。所以独立R操作。这个时候我们引用Repository的概念。Repository我们使用Repository辅助Model,将相关的查询逻辑封装到不同的repository中,方便逻辑代码的维护符合SOLID的单一原则符合SOLID的依赖反转clipboard.pngUserController<?phpnamespace App\Http\Controller; class UserController extends Controller{ public $request; protected $userService; public function __construct(Request $request, UserService $userService) { $this->request = $request; $this->userService = $userService; } public function getUserInfo() { //... validation return $this->userService->getUserInfo ($this->request->all()); } }UserService<?phpnamespace App\Service; class UserService{ public $userRepository; public function __construct(UserRepository $userRepository){ $this->userRepository = $userRepository; } public function getUserInfo() { return $this->userRepository->getUserInfo($data); } }UserRepository<?phpnamespace App\Repository; class UserRepository{ public function getUserInfo($data) { $userId = $data['user_id']; $result = User::where('id',$userId)->first(); return $result; } }解决了R的问题,有人就问了,难道因为CUD比较统一简单就可以放在一起了吗?答案是NO,我们引用一个新的名词Action。Action这是看了@Charlie_Jade的文章才学到的独立每个操作文件,例如CreateUser,DeleteUser,UpdateUser符合SOLID的单一原则clipboard.pngUserController<?phpnamespace App\Http\Controller; class UserController extends Controller{ public $request; protected $userService; public function __construct(Request $request, UserService $userService) { $this->request = $request; $this->userService = $userService; } public function register(){ //... validation return $this->userService->register($this->request->all()); } public function getUserInfo() { return $this->userService->getUserInfo ($this->request->all()); } }UserService<?phpnamespace App\Service; class UserService{ public function getUserInfo(UserRepository $userRepository) { return $this->userRepository->getUserInfo($data); } public function register(){ $result = (new CreateUser())->execute($this->request->all()); return $result; } }UserRepository<?phpnamespace App\Repository; class UserRepository{ public function getUserInfo($data) { $userId = $data['user_id']; $result = User::where('id',$userId)->first(); return $result; } }CreateUser<?phpnamespace App\Action; use App\Model\Member; class CreateUser extends CreateUserWallet { public function execute(array $data) { $models = new Member(); $models->tel = $data['tel']; $models->password = $data['password']; $result = $models->save (); return $result; } }以上代码逻辑见下图clipboard.png除模版(V)等HTML,JS等,还需要一些其他的规则,或者说是方式去实现一些代码的解耦合,以下不再提供代码案例。Common译为公共的,常用的,再部分开发中,你可能需要一些公共的方法(并非公共的类,例如邮件发送等,用他并不合适),比如查询用户余额,查询用户是否注册或者是否在线,生成订单号等。使用Common更要简单。他更像一个公共函数库的样子clipboard.pngEvent不关心执行结果时可以选使用,不过Event的Listen也是提供了队列。Exception不要将你的所有错误提示都使用Return返回,很多时候你的返回未必是你的返回致谢感谢各位同学看完这篇文章,如果你有新的想法欢迎在评论区讨论.参考文章Laravel 的中大型專案架構:http://oomusou.io/laravel/arc...Laravel 程序架构设计思路使用动作类 : https://segmentfault.com/a/11...如何使用 Service 模式? : http://oomusou.io/laravel/ser...面向对象设计的SOLID原则 : https://www.cnblogs.com/shany...
2023年08月10日
18 阅读
0 评论
0 点赞
2023-08-10
PHP异步编程简述
PHP异步编程简述概述异步编程,我们从字面上理解,可以理解为代码非同步执行的。异步编程可以归结为四种模式:回调、事件监听、发布/订阅、promise模式。我们最熟悉的两种模式是回调和事件监听,举两个最简单的javascript例子,一个ajax,一个点击事件的绑定:123$.getJSON("uri", params, function(result) {do_something_with_data(result);});123$("#id").click(function(){do_something_when_user_click_id();});以上两个示例有一个共同的特点,就是把函数当做参数传递给另一个函数。被传递的函数可以被称作为闭包,闭包的执行取决于父函数何时调用它。优势与劣势异步编程具有以下优势:解耦,你可以通过事件绑定,将复杂的业务逻辑分拆为多个事件处理逻辑并发,结合非阻塞的IO,可以在单个进程(或线程)内实现对IO的并发访问;例如请求多个URL,读写多个文件等效率,在没有事件机制的场景中,我们往往需要使用轮询的方式判断一个事件是否产生异步编程的劣势:异步编程的劣势其实很明显——回调嵌套。相信一部分人在写ajax的时候遇到过这样的场景:1234567$.getJSON("uri", params, function(result_1) {$.getJSON("uri", result_1, function(result_2) { $.getJSON("uri", result_2, function(result_3) { do_something_with_data(result_3); }); });;});这样的写法往往是因为数据的依赖问题,第二次ajax请求依赖于第一次请求的返回结果,第三次ajax依赖于第二次。这样就造成深层次的回调嵌套,代码的可读性急剧下降。虽然有一些框架能够通过一些模式解决这样的问题,然并卵,代码的可读性相比同步的写法依然差很多。异步编程的另一个劣势就是编写和调试的过程更加复杂,有时候你不知道什么时候你的函数才会被调用,以及他们被调用的顺序。而我们更习惯同步串行的编程方式。然而,我相信一旦你开始使用异步编程,你一定会喜欢上这种方式,因为他能够带给你更多的便利。PHP异步编程概述在php语言中,异步的使用并不像javascript中那么多,归其原因主要是php一般是在web环境下工作,接收请求->读取数据->生成页面,这看起来天生就是一个串行的过程;所以,在php中,异步并没有广泛使用。在javascript中的4中异步编程模式,均可以在php中实现。回调:1234array_walk($arr, function($key, $value){$value += 1;});print_r($arr);回调的方式,在大多情况下,代码仍然是顺序执行的(array_walk->print_r的顺序)。回调函数的意义在于被传递者可以调用回调函数对数据进行处理,这样的好处在于提供更好的扩展性和解耦。我们可以把这个回调函数理解为一个格式化器,处理相同的数据,当我传递一个json过滤器时,返回的结果可能是一个json压缩过的字符串,当我传递的是一个xml过滤器时,返回的结果可能是一个xml字符串(有点多态的思想)。事件监听(定时器,时间事件):12345678$loop = React\EventLoop\Factory::create();$loop->addPeriodicTimer(5, function () {$memory = memory_get_usage() / 1024; $formatted = number_format($memory, 3).'K'; echo "Current memory usage: {$formatted}\n";});$loop->run();事件监听在PHP中用的并不多,但并不是没有,例如pcntl_signal()监听操作系统信号,以及其他IO事件的监听等等。上面的示例是一个事件事件的侦听,每隔5s中,会执行一次回调函数。在四种异步模式中,事件监听的应用是更有意义的。然我们看一个同步的例子,下面这段代码用于向百度和google(一个不存在的网站)发起请求,同步的编写写法是先去请求百度或者google,等待请求结束后再请求另一个:123$http = new HTTP();echo $http->get('http://www.baidu.com');echo $http->get('http://www.google.com');基于事件的处理方式可以是这样的:1234567$http = new HTTP();$http->get('www.baidu.com');$http->get('www.huyanping.cn');$http->on('response', function($response){echo $response . PHP_EOL;});$http->run();异步的写法允许我们同时处理多个事务,谁先完成,就先去处理谁。一个简单的异步http客户端见:async-http-phpPHP有很多扩展和包提供了这方面的支持:ext-libevent libevent扩展,基于libevent库,支持异步IO和时间事件ext-event event扩展,支持异步IO和时间事件ext-libev libev扩展,基于libev库,支持异步IO和时间事件ext-eio eio扩展,基于eio库,支持磁盘异步操作ext-swoole swoole扩展,支持异步IO和时间,方便编写异步socket服务器,推荐使用package-react react包,提供了全面的异步编程方式,包括IO、时间事件、磁盘IO等等package-workerman workerman包,类似swoole,php编写发布/订阅:12345$lookup = new nsqphp\Lookup\Nsqlookupd;$nsq = new nsqphp\nsqphp($lookup);$nsq->subscribe('mytopic', 'somechannel', function($msg) {echo $msg->getId() . "\n";})->run();promise:12345678910111213141516171819202122232425function getJsonResult(){return queryApi() ->then( // Transform API results to an object function ($jsonResultString) { return json_decode($jsonResultString); }, // Transform API errors to an exception function ($jsonErrorString) { $object = json_decode($jsonErrorString); throw new ApiErrorException($object->errorMessage); } );}// Here we provide no rejection handler. If the promise returned has been// rejected, the ApiErrorException will be throwngetJsonResult()->done(// Consume transformed object function ($jsonResultObject) { // Do something with $jsonResultObject });promise模式的意义在于解耦,就在刚刚我们提到的异步回调嵌套的问题,可以通过promise解决。其原理是在每一次传递回调函数的过程中,你都会拿到一个promie对象,而这个对象有一个then方法,then方法仍然可以返回一个promise对象,通过传递promise对象可以实现把多层嵌套分离出来。具体的代码需要去研究一下源码才可以,有点难懂,PHP的promise推荐阅读:promise异步的实现原理异步的实现大多情况下少不了循环监听事件,例如我们上面看到$loop->run(),这里其实是一个死循环,监听到事件则调用相应的处理函数。如果你对pcntl熟悉,你一定知道declare(tick=1),其实它也是一种循环,含义是每执行tick行代码,则检查一次是否有尚未处理的信号。虽然会有一个阻塞的死循环(大多数情况下,declare属于特殊情况),但我们可以对多个事件进行监听处理,同时可以在某一个事件处理的过程中停止循环,这样就可以实现并发异步的IO访问,甚至更多。一段伪代码如下:12345678910$async = new Async();$async->on('request', function($requset){do_something_with($request);});// 这里其实就是$loop->run()的核心代码while(true){$async->hasRequest() ? $async->callRequestCallback() : null; sleep(1);}总结整片文章其实并不够详细,充其量算是一篇介绍性的文章,算是我在异步编程方面的一次总结。异步编程的学习并不像学习一门语言或者设计模式那样简单,它要求我们改变传统的编程方式。而异步IO对于学习者要求也略高,首先你必须熟悉同步的IO操作,甚至你需要了解一些协议解析的内容。希望上面的内容对于初学者有一些帮助。文中若有错误的地方,还望指正。
2023年08月10日
14 阅读
0 评论
0 点赞
2023-08-10
Ramda函数式编程之PHP
Ramda函数式编程之PHP 0x00 何为函数式编程网上已经有好多详细的接受了,我认为比较重要的有:函数是“第一等公民”,即函数和其它数据类型一样处于平等地位使用“表达式”(指一个单纯的运算过程,总是有返回值),而不是“语句”(执行操作,没有返回值)没有”副作用“,即不修改外部值0x01 开始函数式编程在此之前,请先了解PHP中的匿名函数和闭包,可以参考我写得博客函数式编程有两个最基本的运算:合成和柯里化。函数合成函数合成,即把多个函数的运算合成一个函数,如A=f(x)B=g(x)C=f(g(x))那么C即是A和B的合成。用代码表示为:$compose = function ($f,$g){return function ($x) use($f,$g){ //这里返回一个函数的函数,即高阶函数 return $f($g($x)); };};function addTen($a){return $a+10;}function subOne($a){return $a-1;}$z = $compose('addTen','subOne');//如果使用 $addOne = function(){}的形式,可以直接传变量echo $z(5);// 14要求合成的函数也是个纯函数,如果不是纯函数,那么结果不一致,怎么合成呢?compose返回一个高阶函数,当给合成的这个函数传值时,变回在高阶函数内部调用之前保存的函数。柯里化可以看到如果这里传入的函数参数有多个,那么上面的合成函数就失效了。这里就要请出另外一个函数式编程使用到的另外一个大神了,柯里化。“所谓"柯里化",就是把一个多参数的函数,转化为单参数函数"。//柯里化之前function add($a,$b){return $a+$b;}add(1, 2); // 3// 柯里化之后function addX($b) {return function ($a) use($b) { return $a + $b; };}$addTwo = addX(2);$addTwo(1);//3PHP7以下直接调用addX(2)(1),会报错,所以上面使用了中间变量$addTwo。Parse error: syntax error, unexpected '('在PHP7以上完善了一致变量语法,而且PHP7速度更快,强烈建议使用PHP7。通用柯里化,柯里化很美好,然而我们不可能为每一个函数写一遍,那么有没有包装函数,可以把普通的函数改些为柯里化后的函数呢?代码如下:(摘自:pramda)function curry2($callable){return function () use ($callable) { $args = func_get_args(); switch (func_num_args()) { case 0: throw new \Exception("Invalid number of arguments"); break; case 1: return function ($b) use ($args, $callable) { return call_user_func_array($callable, [$args[0], $b]); }; break; case 2: return call_user_func_array($callable, $args); break; default: // Why? To support passing curried functions as parameters to functions that pass more that 2 parameters, like reduce return call_user_func_array($callable, [$args[0], $args[1]]); break; } };}function add($a,$b){return $a+$b;}$addCurry = curry2('add');$addTwo = $addCurry(2);$addTwo(1);//3说明,curry2返回一个闭包(如上面的$addCurry),当这个闭包被调用时会通过func_get_args动态获取参数,以及func_num_args动态获取参数个数。curry2函如其名,可以给把参数个数为两个函数柯里化。于是在闭包里,我们看到,在对参数个数进行判断,当参数个数为1时,则生成新的闭包(如上面的$addTwo),新的闭包里保存原函数以及整个参数,当新闭包被调用时,则调用call_user_func_array传入原函数、保存的参数、新参数,获取了想要的结果。扩展,函数式编程还有另外一个重要的概念,函子(即带有map方法的类),更多内容可以看阮老师的这两篇文章,我就不详叙了。函数式编程初探函数式编程入门教程平常我们自己使用的函数,如果符合函数式编程的思想,也可以柯里化。当然对于更多参数的函数得运用更高阶的curryN来柯里化。这些已经有人造好轮子了,下面开始进入正题了。0x02 Ramda这个Ramda实际上是函数式编程中的Pointfree风格。在Ramda里,数据一律放在最后一个参数,理念是"function first,data last"。比如//例1function map(){$args = func_get_args(); $n = func_num_args(); $callable = $args[$n-1]; unset($args[$n-1]); $res = []; foreach ($args as $v){ if(is_array($v)){ foreach ($v as $i){ $res[] = call_user_func($callable,$i); } }else{ $res[] = call_user_func($callable,$v); } } return $res;}map(1,2,'square');//1,4map([1,2],'square');// 1,4//例2function square($v){return($v*$v);}array_map("square",[1,2]); //1 ,4上面的代码,例1就不是Ramda风格,而例2则是Ramda风格。既然有人造好轮子了,那么我们直接用就好啦,下面请出主角,pramda,Ramda风格的PHP函数式编程库。安装composer require kapolos/pramda如果出现[InvalidArgumentException]Could not find package kapolos/pramda.可以在composer.json里加入 "kapolos/pramda":"dev-master"示例:$before = [1,2,3,4,5];$after = P::map(function($num) {return $num * 2;}, $before);P::toArray($after); //=> [2,4,6,8,10]$addOne = P::add(1);$divTen = P::divide(10); //10是被除数$fn1 = P::compose($addOne,$divTen); //compose从右往左$fn2 = P::pipe($addOne,$divTen);//pipe从左往右echo $fn1(1); //11echo "\n";echo $fn2(1); //5不足之处,pramda不支持占位符,另外curry函数最多只支持3个参数。另外有也有两个函数式编程库,functional-php和dash可惜不是Ramda风格的。正如阮老师所提到的学习函数式编程,实际上就是学习函子的各种运算。如果想了解更多,可以继续阅读阮老师的这两篇文章。Ramda 函数库参考教程Pointfree 编程风格指南
2023年08月10日
12 阅读
0 评论
0 点赞
1
...
12
13
14
...
20