PHP下的异步尝试三:协程的PHP版thunkify自动执行器
PHP下的异步尝试系列
如果你还不太了解PHP下的生成器和协程,你可以根据下面目录翻阅
- PHP下的异步尝试一:初识生成器
- PHP下的异步尝试二:初识协程
- PHP下的异步尝试三:协程的PHP版thunkify自动执行器
- PHP下的异步尝试四:PHP版的Promise
- [PHP下的异步尝试五:PHP版的Promise的继续完善]
高阶函数
在我们实现自动调度(器)函数前,我们先来理解下高阶函数
thunk函数
# 先求值再传参
function func(m){
return m * 2;
}
f(x + 5);
// 等同于
# 先传参再求值
var thunk = function () {
return x + 5;
};
function func(thunk){
return thunk() * 2;
}
# 这段我们在python或一些语言里,概念叫高阶函数
# 因为php是解释性动态语言,所以函数可以当参数传入
# 这里python,js,php下函数都是可以传参的
PHP版本的thunkify函数
thunkify实现原理:
- 包装一次原始函数名,然后返回一个第一次匿名函数(并携带包装函数): return function () use ($func){$args = func_get_args();}
- 然后再获取该匿名函数的参数,并在上一次第一次匿名函数体内返回一次带回调参数的第二次匿名函数(并携带上一次环境上下文): return function ($callback) use ($args, $func){}
- 调用包装函数,参数为:第一次匿名函数调用的参数+一个回调函数
function thunkify($func){
return function () use ($func) {
$args = func_get_args();
return function ($callback) use ($args, $func) {
array_push($args, $callback);
return $func(...$args);
};
};
};
$printStr = function($p1, $p2, $callback) {
$callback($p1, $p2);
};
$printStrThunkify = thunkify($printStr);
$printStrThunkify(...["foo", "bar"])(function (...$p) {
var_dump($p);
});
# output
array(2) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
}
只能执行一次回调的thunkify函数
function thunkify($func){
return function () use ($func) {
$args = func_get_args();
return function ($callback) use ($args, $func) {
// 原本的获取参数,回调会多次执行
// array_push($args, $callback);
// 增加回调只能执行一次
$callbackCalled = false;
array_push($args, function (...$params) use ($callback, &$callbackCalled) {
if ($callbackCalled) return ;
$callbackCalled = true;
$callback(...$params);
});
return $func(...$args);
};
};
};
$printStr = function($p1, $p2, $callback) {
$callback($p1, $p2);
$callback($p1, $p2); //我们增加一次回调
};
$printStrThunkify = thunkify($printStr);
$printStrThunkify(...["foo", "bar"])(function (...$p) {
var_dump($p);
});
# output
array(2) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
}
看到这里,你可能还在疑惑,thunkify函数其实只是帮我们包装了一次有回调函数的高阶函数而已
不过这里到底有什么用处呢,在普通场景下确实用户不大(可能用处单纯就在做一些前后置函数包装也是用处的,类似python的装饰)
但是,但是,但是在生成器协程里,Thunkify函数可以用于生成器协程的自动流程管理。
生成器协程的自动执行基础理解
每一次yield出来的结果都是一个thunk函数的回调
function thunkify($func){
return function () use ($func) {
$args = func_get_args();
return function ($callback) use ($args, $func) {
$callbackCalled = false;
array_push($args, function (...$params) use ($callback, &$callbackCalled) {
if ($callbackCalled) return ;
$callbackCalled = true;
$callback(...$params);
});
return $func(...$args);
};
};
};
$printStr1 = function($p1, $callback) {
$callback($p1);
};
$printStr2 = function($p1, $callback) {
$callback($p1);
};
$printStrThunkify1 = thunkify($printStr1);
$printStrThunkify2 = thunkify($printStr2);
function gen()
{
global $printStrThunkify1, $printStrThunkify2;
$r1 = yield $printStrThunkify1("1");
var_dump($r1);
$r2 = yield $printStrThunkify2("2");
var_dump($r2);
}
$gen = gen();
// 手动回调, 模拟自动执行基础理解
$value = $gen->current();
$value(function ($p1) use($gen) {
$value = $gen->send($p1);
$value(function ($p1) use($gen) {
$value = $gen->send($p1);
var_dump($value);
});
});
自动执行器
我们这里只是实现上面的手动回调执行
增加了一个自动执行器,把生成器协程传入后讲自动执行生成器协程
function thunkify($func){
return function () use ($func) {
$args = func_get_args();
return function ($callback) use ($args, $func) {
$callbackCalled = false;
array_push($args, function (...$params) use ($callback, &$callbackCalled) {
if ($callbackCalled) return ;
$callbackCalled = true;
$callback(...$params);
});
return $func(...$args);
};
};
};
$printStr1 = function($p1, $callback) {
sleep(2);
$callback($p1);
};
$printStr2 = function($p1, $callback) {
sleep(5);
$callback($p1);
};
$printStrThunkify1 = thunkify($printStr1);
$printStrThunkify2 = thunkify($printStr2);
function gen()
{
global $printStrThunkify1, $printStrThunkify2;
$r1 = yield $printStrThunkify1("1");
var_dump($r1);
$r2 = yield $printStrThunkify2("2");
var_dump($r2);
}
function autoCaller(\Generator $gen)
{
// 注意这里的$next use 引入作用域必须带上&, 否则无法识别
$next = function ($p1) use ($gen, &$next) {
if (is_null($p1)) { //此处获取第一次yeild的回调
$result = $gen->current();
} else {
// send后返回的是下一次的yield值
$result = $gen->send($p1);
}
// 是否生成器迭代完成
// 迭代器生成完成,不再迭代执行(自动执行器返回停止)
if (!$gen->valid()) {
return ;
}
$result($next);
};
$next(null);
}
$gen1 = gen();
//$gen2 = gen();
autoCaller($gen1);
//autoCaller($gen2);
# output
string(1) "1"
string(1) "2"
# 如果我们打开上面的两个sleep()注释
# output
# 等待2秒
string(1) "1"
# 等待5秒
string(1) "2"
# 因为这里我们的thunk里执行的实际函数是同步的代码,所以整体是阻塞的后续代码执行的
总结
只要执行 autoCaller 函数,生成器就会自动迭代完成。这样一来,异步操作不仅可以写得像同步操作,而且一行代码就可以执行。
Thunkify函数并不是 生成器协程 函数自动执行的唯一方案。
因为自动执行的关键是,必须有一种机制,自动控制 生成器协程 函数的流程,接收和交还程序的执行权。
回调函数可以做到这一点,Promise 对象也可以做到这一点。本系列的下一篇,将介绍基于PHP的Promise实现的自动执行器。
附录参考
原文地址:
PHP下的异步尝试三:协程的PHP版thunkify自动执行器的更多相关文章
- PHP下的异步尝试二:初识协程
PHP下的异步尝试系列 如果你还不太了解PHP下的生成器,你可以根据下面目录翻阅 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunkify ...
- PHP下的异步尝试四:PHP版的Promise
PHP下的异步尝试系列 如果你还不太了解PHP下的生成器和协程,你可以根据下面目录翻阅 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunk ...
- PHP下的异步尝试一:初识生成器
PHP下的异步尝试系列 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunkify自动执行器 PHP下的异步尝试四:PHP版的Promise ...
- 进程&线程(三):外部子进程subprocess、异步IO、协程、分布式进程
1.外部子进程subprocess python之subprocess模块详解--小白博客 - 夜风2019 - 博客园 python subprocess模块 - lincappu - 博客园 之前 ...
- Python异步IO之协程(一):从yield from到async的使用
引言:协程(coroutine)是Python中一直较为难理解的知识,但其在多任务协作中体现的效率又极为的突出.众所周知,Python中执行多任务还可以通过多进程或一个进程中的多线程来执行,但两者之中 ...
- (并发编程)进程池线程池--提交任务2种方式+(异步回调)、协程--yield关键字 greenlet ,gevent模块
一:进程池与线程池(同步,异步+回调函数)先造个池子,然后放任务为什么要用“池”:池子使用来限制并发的任务数目,限制我们的计算机在一个自己可承受的范围内去并发地执行任务池子内什么时候装进程:并发的任务 ...
- Python的异步编程[0] -> 协程[1] -> 使用协程建立自己的异步非阻塞模型
使用协程建立自己的异步非阻塞模型 接下来例子中,将使用纯粹的Python编码搭建一个异步模型,相当于自己构建的一个asyncio模块,这也许能对asyncio模块底层实现的理解有更大的帮助.主要参考为 ...
- day37 异步回调和协程
异步回调 """ 异步任务使用场景 爬虫 1.从目标站点下载网页数据 本质就是HTML格式字符串 2.用re从字符串中提取出你需要的数据 ""&quo ...
- 异步IO(协程,消息循环队列)
同步是CPU自己主动查看IO操作是否完成,异步是IO操作完成后发出信号通知CPU(CPU是被通知的) 阻塞与非阻塞的区别在于发起IO操作之后,CPU是等待IO操作完成再进行下一步操作,还是不等待去做其 ...
随机推荐
- [luogu1463 HAOI2007] 反素数 (约数)
传送门 Description 对于任何正整数x,其约数的个数记作g(x).例如g(1)=1.g(6)=4. 如果某个正整数x满足:g(x)>g(i) 0<i<x,则称x为反质数.例 ...
- 理解 Javascript 执行上下文和执行栈
如果你是一名 JavaScript 开发者,或者想要成为一名 JavaScript 开发者,那么你必须知道 JavaScript 程序内部的执行机制.理解执行上下文和执行栈同样有助于理解其他的 Jav ...
- 【Codeforces 276C】Little Girl and Maximum Sum
[链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 最后的和. 其实可以看成是 ∑bi*ai 的形式. 这里的bi这个系数表示的是有多少个区间覆盖了ai这个元素. 既然这样的话. 那么 ...
- Android 经常使用设计模式(一)
由于项目变更的频繁性,作为一名程序猿,我们须要掌握设计模式的必要性.就不言而喻~~.以下就是一些我自己学习的设计模式总结. 接下来,主要是针对几个比較经常使用模式进行解说,主要是以下几种: 观察者模式 ...
- 关于部门后端所有转向java前初步设想
Java服务有些什么形式?眼下来看主要是下面几类: 1. 执行在Web应用server的Servlet 2. Thrift.PB.Avro等相似框架写的java服务 3. WebService( ...
- 如何在IE浏览器里模仿DomContentLoaded
稍微了解一点框架的事件绑定的都知道 window.onload 事件需要在页面所有内容(包括图片.flash.iframe等)加载完后,才执行,但往往我们更希望在 DOM 一加载完就执行脚本,而各大框 ...
- insmod: error inserting 'hello.ko': -1 Invalid module format
在学习编写linux驱动程序的时候,一般都是从写一个helloworld的模块開始. 可是在编译完毕后,进行模块载入的时候,有时会出现例如以下错误: insmod: error inserting ' ...
- 创建多线程的HttpClient
在实际的应用中,我们的联网应用程序里应该有一个HttpClient,并将其用于所有的HTTP通信.这就可能在同一个Http Client同时发出多个请求,也就产生了多线程的问题.幸运的是,在HttpC ...
- redis windows安装与使用
是什么 Redis(Remote Dictionary Server)远程字典服务器 开源免费 C语言编写的 key/value分布式内存数据库,基于内存运行 Redis支持数据的持久化,可以将内存中 ...
- Spark RDD概念学习系列之Pair RDD的transformation操作
不多说,直接上干货! Pair RDD的transformation操作 Pair RDD转换操作1 Pair RDD 可以使用所有标准RDD 上转化操作,还提供了特有的转换操作. Pair RDD转 ...