PHP下的异步尝试系列

如果你还不太了解PHP下的生成器和协程,你可以根据下面目录翻阅

  1. PHP下的异步尝试一:初识生成器
  2. PHP下的异步尝试二:初识协程
  3. PHP下的异步尝试三:协程的PHP版thunkify自动执行器
  4. PHP下的异步尝试四:PHP版的Promise
  5. [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实现原理:

  1. 包装一次原始函数名,然后返回一个第一次匿名函数(并携带包装函数): return function () use ($func){$args = func_get_args();}
  2. 然后再获取该匿名函数的参数,并在上一次第一次匿名函数体内返回一次带回调参数的第二次匿名函数(并携带上一次环境上下文): return function ($callback) use ($args, $func){}
  3. 调用包装函数,参数为:第一次匿名函数调用的参数+一个回调函数

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实现的自动执行器。

附录参考

Thunk 函数的含义和用法 - 阮一峰

原文地址:

PHP下的异步尝试三:协程的PHP版thunkify自动执行器的更多相关文章

  1. PHP下的异步尝试二:初识协程

    PHP下的异步尝试系列 如果你还不太了解PHP下的生成器,你可以根据下面目录翻阅 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunkify ...

  2. PHP下的异步尝试四:PHP版的Promise

    PHP下的异步尝试系列 如果你还不太了解PHP下的生成器和协程,你可以根据下面目录翻阅 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunk ...

  3. PHP下的异步尝试一:初识生成器

    PHP下的异步尝试系列 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunkify自动执行器 PHP下的异步尝试四:PHP版的Promise ...

  4. 进程&线程(三):外部子进程subprocess、异步IO、协程、分布式进程

    1.外部子进程subprocess python之subprocess模块详解--小白博客 - 夜风2019 - 博客园 python subprocess模块 - lincappu - 博客园 之前 ...

  5. Python异步IO之协程(一):从yield from到async的使用

    引言:协程(coroutine)是Python中一直较为难理解的知识,但其在多任务协作中体现的效率又极为的突出.众所周知,Python中执行多任务还可以通过多进程或一个进程中的多线程来执行,但两者之中 ...

  6. (并发编程)进程池线程池--提交任务2种方式+(异步回调)、协程--yield关键字 greenlet ,gevent模块

    一:进程池与线程池(同步,异步+回调函数)先造个池子,然后放任务为什么要用“池”:池子使用来限制并发的任务数目,限制我们的计算机在一个自己可承受的范围内去并发地执行任务池子内什么时候装进程:并发的任务 ...

  7. Python的异步编程[0] -> 协程[1] -> 使用协程建立自己的异步非阻塞模型

    使用协程建立自己的异步非阻塞模型 接下来例子中,将使用纯粹的Python编码搭建一个异步模型,相当于自己构建的一个asyncio模块,这也许能对asyncio模块底层实现的理解有更大的帮助.主要参考为 ...

  8. day37 异步回调和协程

    异步回调 """ 异步任务使用场景 爬虫 1.从目标站点下载网页数据 本质就是HTML格式字符串 2.用re从字符串中提取出你需要的数据 ""&quo ...

  9. 异步IO(协程,消息循环队列)

    同步是CPU自己主动查看IO操作是否完成,异步是IO操作完成后发出信号通知CPU(CPU是被通知的) 阻塞与非阻塞的区别在于发起IO操作之后,CPU是等待IO操作完成再进行下一步操作,还是不等待去做其 ...

随机推荐

  1. redi通过哨兵sentinel实现主从切换

    本次实验主要为了让哨兵监听redis主从复制,当主节点关闭后,哨兵会选举一台从节点成为主节点,并且让其他从节点变成新主节点得从节点 本次理论需要三台机器,一主两从,为了方便用一台服务器开启三个实例,一 ...

  2. 10行Python代码实现人脸定位

    10行python机器学习全卷机网,实现100+张人脸同时定位! 发表评论 1,049 游览 A+ 所属分类:未分类 收  藏 今天介绍一个快速定位人脸的深度学习算法MTCNN,全称是:Multi-t ...

  3. 【codeforces 803F】Coprime Subsequences

    [题目链接]:http://codeforces.com/contest/803/problem/F [题意] 给你一个序列; 问你这个序列里面有多少个子列; 且这个子列里面的所有数字互质; [题解] ...

  4. log4j输出多个自定义日志文件,动态配置路径

    Log4J的配置文件(Configuration File)就是用来设置记录器的级别.存放器和布局的,它可接key=value格式的设置或xml格式的设置信息.通过配置,可以创建出Log4J的运行环境 ...

  5. C#中的string驻留池

    刚开始学习C#的时候,就听说CLR对于String类有一种特别的内存管理机制:有时候,明明声明了两个String类的对象,但是他们偏偏却指向同一个实例.如下: String s1 = "He ...

  6. [Node.js] Manage Configuration Values with Environment Variables

    Storing configuration in files instead of the environment has many downsides, including mistakenly c ...

  7. java 顺序 读写 Properties 配置文件 支持中文 不乱码

    java 顺序 读写 Properties 配置文件 ,java默认提供的Properties API 继承hashmap ,不是顺序读写的. 特从网上查资料,顺序读写的代码,如下, import j ...

  8. spring Batch实现数据库大数据量读写

    spring Batch实现数据库大数据量读写 博客分类: spring springBatchquartz定时调度批处理  1. data-source-context.xml <?xml v ...

  9. Android 对话框(Dialog) 及 自己定义Dialog

    Activities提供了一种方便管理的创建.保存.回复的对话框机制,比如 onCreateDialog(int), onPrepareDialog(int, Dialog), showDialog( ...

  10. java位运算笔记

    位运算: ~(非)-->二进制数进行0和1的互换 样例: public class Test { public static void main(String[] args) { System. ...