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. [tyvj2054] 四叶草魔杖 (最小生成树 状压dp)

    传送门 Background 陶醉在彩虹光芒笼罩的美景之中,探险队员们不知不觉已经穿过了七色虹,到达了目的地,面前出现了一座城堡和小溪田园,城堡前的木牌上写着"Poetic Island&q ...

  2. [Linux]第一部分-认识Linux及Linux主机规划与安装

    ctrl + alt + f1~f6 切换六个终端ctrl + alt + f7 图形化界面 startx 开启x-window桌面 ls -al /root 列出root目录 date日期 +%y/ ...

  3. spring4和hibernate4.0.0的整合

    1.在myeclipse以下创建一个javaproject或者webproject,我创建的时webproject,用的myeclipse2013 2.导入spring的依赖包 3.导入hiberna ...

  4. linux入门基础——linux用户基础

    这篇内容是linux用户基础,相关内容见linux改动username和ubuntu改动username和主机名. 用户.组 当我们使用linux时,须要以一个用户的身份登入,一个进程也须要以一个用户 ...

  5. Android測试APP工具(一)

    近期面试APP开发者的时候,遇到了技术总监问 APP測试的概念性问题.后面感觉主要的项目流程.项目逻辑.屏幕适配. 測试是全然没有问题的.可是对于APP的性能測试.压力測试等高端的測试.还是存在着美中 ...

  6. Share Your Knowledge and Experiences

     Share Your Knowledge and Experiences Paul W. Homer FRoM All oF ouR ExpERiEnCES, including both suc ...

  7. 线程基础:JDK1.5+(8)——线程新特性(上)

    1.概要 假设您阅读JAVA的源码.出现最多的代码作者包含:Doug Lea.Mark Reinhold.Josh Bloch.Arthur van Hoff.Neal Gafter.Pavani D ...

  8. 稀疏编码(Sparse Coding)的前世今生(二)

    为了更进一步的清晰理解大脑皮层对信号编码的工作机制(策略),须要把他们转成数学语言,由于数学语言作为一种严谨的语言,能够利用它推导出期望和要寻找的程式.本节就使用概率推理(bayes views)的方 ...

  9. comp.lang.javascript FAQ [zz]

    comp.lang.javascript FAQ Version 32.2, Updated 2010-10-08, by Garrett Smith FAQ Notes 1 Meta-FAQ met ...

  10. SAM学习笔记

    SAM学习笔记 后缀自动机(模板)NSUBSTR(Caioj1471 || SPOJ 8222) [题意] 给出一个字符串S(S<=250000),令F(x)表示S的所有长度为x的子串中,出现次 ...