武侠小说中,主人公之所以能纵横江湖,常常离不开一样可遇不可求的绝世法宝——武功秘籍。如今勇于尝试的开发者,笃定地告诉后来者:选Luat二次开发,就如同拥有了物联网开发的武功秘籍。

本期让我们通过《射雕英雄传》的一些小场景,聊聊LuaTask延时那些事儿~

不了解Luat开发的朋友,可参考学习:

稀饭放姜大神《LuaTools上手教程》

http://doc.openluat.com/article/1719/0

晨旭大神《Luat入门教程》

http://doc.openluat.com/wiki/3?wiki_page_id=606


什么是协程?

首先我们来看下什么是协程:

提起协程的话,大多数时候都会跟多线程进行比较。两者之间是有些相似的地方,都是程序上下文切换执行,都有并发性,但更多的还是区别所在。

多线程——是发生在系统态的程序切换,可以模拟较为真实的并发性。因为本身执行顺序带有一定的随机性,不可预测,所以很容易因为竞争造成数据混乱。

协程——是发生在用户态上的程序切换,只是对多线程的一种模拟,并不能替代多线程。它把本该在一个地方实现的代码拆分到了不同任务,让逻辑表述看起来更加清晰。

在《射雕英雄传》第十五章神龙摆尾中,有关黄蓉与欧阳克的比试有这样一段描写:

黄蓉走进圈子,道:“咱们是文打还是武打?”

欧阳克心道:“偏你就有这许多古怪。”问道:“文打怎样?武打怎样?”

黄蓉道:“文打是我发三招,你不许还手;你还三招,我也不许还手。武打是乱打一气,你用死蛇拳也好,活耗子拳也好,都是谁先出圈子谁输。”

这个比试所定的规则,跟我们说的协程和多线程的关系是比较像的:

协程就是文打,执行权在两人之间交替运行,同一时间只能有一人挥拳,A打完之后B才能打。

多线程就是武打,两个人可以同时挥拳,互不影响。

如果用程序表示协程的话,代码可能会像下面这样:

Luat协程使用示例


function A()
for i=1, 5 do
coroutine.resume(co)
print("A\t"..i)
end
end function B()
for i=1, 5 do
coroutine.yield()
print("B\t"..i)
end
end co = coroutine.create(B)
A()
--[[
A 1
B 1
A 2
B 2
A 3
B 3
A 4
B 4
A 5
]]

这是一种简单的使用场景,yield和resume只是负责切换控制,让控制权在两个任务之间来回切换,达到了使两个任务 “并行” 的目的。


协程的延时

协程的概念说完了,但是...这跟延时有什么关系?

其实稍加思索的话,就会发现这个黄蓉与欧阳克约定的文打规则里有个BUG:

黄蓉先打三拳,然后欧阳克还三招,但是如果黄蓉只打了两拳就停手了,欧阳克怎么办?

这就反映出协程中一个很现实的问题:

各个协程之间的延时是会相互影响的,你打了一拳去睡觉了,那我也必须跟着等,我回一拳之后也去睡觉了,你也必须跟着等。

如果反映到程序上,代码就是这样:

function wait(ms)
-- 阻塞延时, 仅仅只做说明, 并不实现
end function A()
for i=1, 5 do
coroutine.resume(co)
print("A\t"..i)
wait(1000)
end
end function B()
for i=1, 5 do
coroutine.yield()
print("B\t"..i)
wait(1000)
end
end co = coroutine.create(B)
A()

我们期望两个协程都能以1000ms的延时打印输出,但是这种阻塞延时其实是会在两个协程之间相互影响的:

A 在延时的过程中其实是会加长 B 的延时,最终两个协程都会以2000ms的延时打印输出。

这当然是一件很不合理的情况,我们希望代码中的程序段都应该保持一定的独立性,不能在修改一段代码之后莫名其妙的影响了另一段代码的执行情况。


概念的偏差

人们常说,比喻都是跛脚的。

在学习一些概念的时候,我们常常会借助一些比喻描述,加深理解。但是例子并不能完全做到等同,仅仅只能帮助理解某一面特性而已。

如果完全类比,或者把这个例子当成是概念本身,那很多细节是经不起推敲的。

黄蓉与欧阳克约定文打之后,欧阳克为什么一定要遵守?黄蓉在打出一拳之后,欧阳克为什么不能动?他完全可以毁约啊。

这就牵扯到协程和多线程的另一个重要概念了:执行权。

你可以把它想像成是一个令牌,只有拿到令牌的人才能指挥行动。

开始令牌在黄蓉手上,所以她可以行动,欧阳克手上没有令牌,所以只能站着挨打,黄蓉打完之后把令牌丢给了欧阳克,欧阳克才开始还手。

武打就是两人手上都有一个令牌,所以两个人才能乱打一气,同时挥拳。

有了上面这些基础概念之后,我们可以开始尝试理解问题本身了。


真实的协程

那么真实的协程到底是什么样子?

在单任务系统,依靠切换控制权来模拟多线程的话,那延时必定不可能是真正“延时”。

一个任务的阻塞延时肯定会干扰到其他任务的计时,为了解决这个问题,我们一般会引入一个第三者来进行时间管理,也就是“时钟调度器”

其实A、B两个任务在执行时,总共有四个角色存在:Core(底层)、以及Lua中的A任务B任务,还有run (一段控制执行权的代码)。

Core本身带一个系统时钟,用来记录当前时间,同时维护了一个表格,用来记录在什么时间,把控制权交给谁。

A、B在睡觉前都把自己的唤醒时间告诉Core,把自己的控制权交给run。run在获取到唤醒时间之后交给指定任务执行,指定任务执行完毕之后交还控制权。

当然,这里其实还有另外一个疑问,

为什么要有 run 这个角色的存在?

按照我们直观的理解,为了防止AB在睡觉时霸占控制权,仅仅只需要一个管理者就可以了。时间到达之后,管理者可以自行调用A 或B,它们在执行完毕之后交还控制权,这样看起来似乎更简单。

这样做其实有两方面原因:

一是因为除了时钟消息,Core还有其他消息需要传递给Lua,程序中不光是有 A 和 B 提交的时钟消息,所以需要有一个角色来处理这些其他信息。

二是因为Core和 A、B 两个任务所属在不同层,Core在C 层,而任务A、B是在Lua层。为了简化两个层之间的交互,Core 把消息传递给Lua之后由Lua自行处理调度。所以是Core处理时钟调度信息,Lua负责调度。


完整演示

最后,我们再看一遍整个过程的完整演示:

延时运行图示说明

从上图可以看到,控制权首先是在 Core,Core 完成初始化工作之后把控制权交给任务A执行。

A任务在执行到延时 wait(100) 时会把当前延时时间加上系统时钟时间的数值,连同自身ID,添加进Core的时间链表。也就是告诉 Core,在130这个时间点,把控制权交给A任务。如下图所示:

然后到了任务B——任务B在执行到 wait(80) 时,也会向 Core 中添加消息, 在110这个时间点,把控制权交给B任务:

接着会按顺序执行到run,run把控制权交给 Core:

然后,Core会以1ms的间隔独自计时,每过1ms,Core都会检查链表第一项时间是否达到。当时钟计时达到时间,控制权会交还给run,并且告诉 run,B的计时时间到了。



之后run会把控制权交给B,B执行完返还控制权。当然 B 在执行过程中依然会向Core中添加消息。

点击放大查看:

接着就开始下一轮循环,如此往复…

点击放大查看:

通过上面的分析我们也不难发现,Lua层当中的所有代码几乎都是瞬时完成的,所有延时操作都是把控制权移交到了Core。

上海合宙通信模块 - 合宙Luat,让万物互联更简单

合宙Luat | 一文读懂LuaTask延时,看我如何从《射雕英雄传》角度分析。的更多相关文章

  1. 一文读懂Spring动态配置多数据源---源码详细分析

    Spring动态多数据源源码分析及解读 一.为什么要研究Spring动态多数据源 ​ 期初,最开始的原因是:想将答题服务中发送主观题答题数据给批改中间件这块抽象出来, 但这块主要使用的是mq消息的方式 ...

  2. 一文读懂HTTP/2及HTTP/3特性

    摘要: 学习 HTTP/2 与 HTTP/3. 前言 HTTP/2 相比于 HTTP/1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作,当然兼容问题以及如何 ...

  3. 一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现

    一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现 导读:近日,马云.马化腾.李彦宏等互联网大佬纷纷亮相2018世界人工智能大会,并登台演讲.关于人工智能的现状与未来,他们提出了各自的观点,也引 ...

  4. 一文读懂高性能网络编程中的I/O模型

    1.前言 随着互联网的发展,面对海量用户高并发业务,传统的阻塞式的服务端架构模式已经无能为力.本文(和下篇<高性能网络编程(六):一文读懂高性能网络编程中的线程模型>)旨在为大家提供有用的 ...

  5. 从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

    本文原作者阮一峰,作者博客:ruanyifeng.com. 1.引言 HTTP 协议是最重要的互联网基础协议之一,它从最初的仅为浏览网页的目的进化到现在,已经是短连接通信的事实工业标准,最新版本 HT ...

  6. 一文读懂 深度强化学习算法 A3C (Actor-Critic Algorithm)

    一文读懂 深度强化学习算法 A3C (Actor-Critic Algorithm) 2017-12-25  16:29:19   对于 A3C 算法感觉自己总是一知半解,现将其梳理一下,记录在此,也 ...

  7. [转帖]MerkleDAG全面解析 一文读懂什么是默克尔有向无环图

    MerkleDAG全面解析 一文读懂什么是默克尔有向无环图 2018-08-16 15:58区块链/技术 MerkleDAG作为IPFS的核心数据结构,它融合了Merkle Tree和DAG的优点,今 ...

  8. [转帖]一文读懂 HTTP/2

    一文读懂 HTTP/2 http://support.upyun.com/hc/kb/article/1048799/ 又小拍 • 发表于:2017年05月18日 15:34:45 • 更新于:201 ...

  9. [转帖]从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

    从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路   http://www.52im.net/thread-1709-1-2.html     本文原作者阮一峰,作者博客:r ...

随机推荐

  1. PHP基础-PHP中的函数声明

    /* PHP的变量的范围* 局部变量: 在函数中声明的变量就是局部变量,只能在自己的函数内部使用.* 全局变量: 在函数外声明,在变量声明以后的,直到整个脚本结束前都可以使用,包括在函数中和{}中都可 ...

  2. algorithm库介绍之---- stable_sort()方法 与 sort()方法 .

    文章转载自:http://www.cnblogs.com/ffhajbq/archive/2012/07/24/2607476.html 关于stable_sort()和sort()的区别: 你发现有 ...

  3. .Net·快速查找哪一个类库引用了哪一个Package

    阅文时长 | 0.18分钟 字数统计 | 348.8字符 主要内容 | 1.引言&背景 2.查找法示例 3.声明与参考资料 『.Net·快速查找哪一个类库引用了哪一个Package』 编写人 ...

  4. Git安装教程最新版本(国内gitee国外github)

    Git安装教程最新版本(国内gitee国外github) 欢迎关注博主公众号「Java大师」, 专注于分享Java领域干货文章, 关注回复「资源」, 获取大师使用的typora主题: http://w ...

  5. Linux_控制作业(管理)

    一.作业控制 1.作业控制与回话 1️⃣:作业控制是shell的一种功能,它允许单个shell实例运行和管理多个命令 2️⃣:作业与在sehll提示符中输入的每个管道相关联.该管道中的所有进程均是作业 ...

  6. ValueError: not enough values to unpack (expected 2, got 1)

    在python中使用字符串分片时遇到这个问题 [ValueError: not enough values to unpack (expected 2, got 1)] --------------& ...

  7. yum 命令详解-yum仓库配置文件详解

    yum安装的优点 1.必须得有网络,通过网络获取软件. 2.管理rpm包 3.自动解决依耐 4.命令简单好用 5.生产最佳实践 yum命令详解 # linux安装软件的三种方式 1.rpm安装 2.源 ...

  8. 问题解决: PythonStudy 环境搭建

    环境搭建的时候遇到问题 参见帖子: http://www.xitongcheng.com/jiaocheng/dnrj_article_24923.html 虚拟机运行的时候会遇到 最近有用户发现在电 ...

  9. php反转字符串的三种方法

    (假设有字符串abcd,用php实现字符串翻转) 1.第一种php有自带的函数strrev可以轻松实现: $str = 'abcd'; //第一种自带strrev实现翻转 echo strrev($s ...

  10. 06.ElementUI 2.X 源码学习:源码剖析之工程化(一)

    0x.00 前言 在用了5章篇幅 ElementUI源码学习:从零开始搭建Vue组件库汇总 讲解了如何编写一个组件.发布npm以及生成展示文档之后.接下来将分析Element项目的代码结构,学习其工程 ...