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. 提高生产力:Web前端验证的标准化

    统一验证标准,减少重复劳动,提高生产力. 当公司内部有多个Web项目的时候,统一验证标准就很有必要了.统一不同项目的验证规则,比如 同为用户名 使用同一套标准,甚至用户名和机构名等也使用同一套标准.( ...

  2. redis_2 数据类型

    1.key Redis keys 命令 下表给出了与 Redis 键相关的基本命令: 序号 命令及描述 1 DEL key该命令用于在 key 存在时删除 key. 2 DUMP key 序列化给定 ...

  3. 有关文档碎片(document fragment)的使用方法

    通常情况下改动.删除或者添加DOM元素. 更新DOM会导致浏览器又一次绘制屏幕,也会导 致reflow,这样会带来巨大的开销.我们通常解决这的办法尽量降低更新DOM.这也就意 味着将DOM的改变分批处 ...

  4. MySQL 5.7.10最新版本号源码安装具体过程

    ,重置密码 利用mysqladmin重置密码 [root@wgq_idc_mon_1_12 mysql5710]#./bin/mysqladmin -h localhost -uroot passwo ...

  5. 转:IOS远程推送通知

    在ios系统中,app应用程序无法在后台完成较多的任务,仅仅允许程序做一些有限的任务(如音视频播放.地理位置信息.voip).然而,如果你想做 一些有趣的事情,并且告知用户,甚至用户没有使用你的app ...

  6. 遗传奥秘的伟大揭秘者:J.Watson

    J.Watson的近照: 人们公认,揭秘生命体的遗传奥秘(DNA)是二十世纪最伟大的科技成果之中的一个,或许就是人类最伟大的科技进步(而不是"之中的一个"). 上世纪是人类做出伟大 ...

  7. POJ 题目3461 Oulipo(KMP)

    Oulipo Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 26479   Accepted: 10550 Descript ...

  8. 利用DBMS_SQLTUNE优化SQL

    DBMS_SQLTUNE优化SQL是在oracle10g才出来的新特性,使用它能很大程度上方便对sql的分析和优化.执行DBMS_SQLTUNE包进行sql优化需要有advisor的权限: stat& ...

  9. tomcat指定JDK版本

    在windows环境下以批处理文件方式启动tomcat,只要运行<CATALINA_HOME>/bin/startup.bat这个文件,就可以启动Tomcat. 在启动时,startup. ...

  10. Spark Streaming概念学习系列之SparkStreaming的高层抽象DStream

    不多说,直接上干货! SparkStreaming的高层抽象DStream 为了便于理解,Spark Streaming提出了DStream抽象,代表连续不断的数据流. DStream 是一个持续的R ...