同步操作将从 deepinwiki/wiki 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
为什么需要学习 lua 语言。 lua 是一个脚本语言,它和 js 这种 html 默认支持的脚本语言比有什么优势?
我感觉是 lua 比 js 更加小巧简单。js 其实算是比较复杂的脚本语言,它的思想是参考同样很累赘的 java。这类庞杂语法的语言,在现在这个时代,变得没有那么受欢迎了。不过 js 默认是浏览器支持,这点优势无比巨大。但是浏览器 wasm 这个通用字节码方向出来后,其他脚本语言其实也有在浏览器作为前端开发的前景。
lua 自身的简练,说不定将来会是 js 的有力竞争对手。 lua 传统上是作为游戏开发的嵌入式语言,它和 c 库有很好的整合能力。对于本地应用来说,相较 js 是传统优势项目。
和本地的脚本语言,如 bash 之类的比较,lua 更加正式,更像一个传统的编程语言,带有不错的标准库,执行效率在脚本语言中几乎是最高效的。和 python 比较,虽然没有 python 那么庞大的第三方库,但 lua 的优势还是精炼和小巧,性能也比较突出。本来,用脚本语言为啥会搞那么多庞大的库也是比较奇怪的事。这种大工程难道不应该用更严肃的语言,比如 go 之类的编写么,所以 python 的发展也有点疲态。
在嵌入式 iot 场合,lua 也有一些不错的支持。
总结,lua 的优势就是它小,高性能,和 c 库底层结合好,能做好脚本的份内工作。
# 安装后可以这样运行一条 lua 语句
lua -e "print(utf8.len('你好'))"
lua prog.lua # 运行文件
#!/usr/bin/env lua
lua # 交互式,非常适合用来学习语法
dofile("lib1.lua") # 在交互式模式下,运行文件
lua 是一个动态语言,它对类型的要求不是那么严格。优点是编程便利,缺点容易出现数值和字符串自动转换之类的错误。尤其比较关键的算法,还是要特别仔细的检查数据类型。
注释:
-- 单行注释
--[[多行注释]]
运算符:
^
: 幂运算,左结合。 -x^2 == -(x^2)
..
: 字符串拼接#
: 长度~=
: 不等a,b = b,a
: 多重赋值整数:64 位有符号数。
浮点:双精度。精度53位整数。
字符串: 不可变类型。
[[多行字符串]]
\z
: 在字符串源代码上跳过空白。(为了书写多行字符串)utf8.len()
: 替代 string.len()
,能正确处理中文。等等类型转换:
"10" + 1 == 11.0
tonumber("3") == 3
tonumber("10e4") == 10000.0
tonumber("10e") == nil
not nil == true
3.3 | 0 == 3
类型:
唯一的数据结构,可表示数组、集合、记录、包(package)、对象等。表是一个指向动态内存的指针,由运行时负责资源管理。
a = {} -- 空表
a["hi"] = 123 -- 随意扩充成员
a[42.0] = "hello" -- 索引是任意非空类型
a[42] = "hi" -- 作为键时,数值会自动转换成整型或浮点,即 42.0 => 42
print(a.hi) -- 123
-- 列表式(从 1 开始索引)。数组
b = {"a","b","c"}
-- 记录式
c = {x=7, y=8, z=9}
-- 特殊字段名
d = {["+"] = "add", ["-"] = "sub"}
遍历:
t = {1,2,nil,3}
for k,v in pairs(t) do -- 获取键 k、值 v,另外 ipairs() 按顺序索引
print (k, v)
end
for k = 1, #t do -- 空洞(hole)也会被读取,即 t[3] = nil
print(k, t[k]) -- 我认为这是一个设计错误,因为 nil 表示不存在
end
安全访问:
例: a.b 如果 a 本身是 nil ,那么就是不安全的访问操作。
解决这个问题的思路:给出不满足时该做什么。而不是只想着满足时做什么,这样可能会导致潜在的逻辑错误。
other = { b=1 }
-- or:当 a 是 nil 时,返回 other
x = a or other -- 给出替代品,或中断报错
c = x.b
-- c = a.b.c.d
for i=1, 1 do
if not a then break end -- a
if not a.b then break end -- a.b
if not a.b.c then break end -- a.b.c
c = a.b.c.d
print(c)
end
因为表是 lua 中的对象,它甚至相当于其他语言各种语言要素的集合体,所以必须要理解如何在 lua 中表达其他语言的概念。
-- 数组:从 1 开始
a = {1,2,3,4} -- a[0] = nil, a[1] = 1, a[2] = 2...
a[0] = 0 -- 将会添加 k = 0, v = 0 的新键值对,但并不会被认为是数组的一部分
-- 对象:表就是一个对象,键值对,如果值为 nil ,该键值对就被视为不存在
a[0] = nil -- 删除该键值对
a[0] = function() return 1 -- 元素可以是任意类型,甚至是另一个表
b = a -- 对象是指针,所以他们是在操作同一个对象,同时改变
-- 克隆对象是复杂的操作,需要对对象模型深入理解
-- 1. 展开对象的成员,获得键值对
-- 2. 设置新对象的键值对 = 该键值对
-- 3. 当该建是对象时选择是否克隆(包括键和值都可能是对象)
-- 相关技术
b = {table.unpack(a)} -- 展开成值的序列,对一般数组而言足够
b = {}
for k,v in pairs(a) do b[k] = v end -- 获得键值对,并设置键值对,但不对键值对是对象的时候做任何处理
-- 如果不是数组或者键值对的复制,而是真正的对象的复制,那么应该由对象自身实现拷贝函数更为合理,而不是用外部的观点去实现对象的复制,增加的部分可能是对成员为对象时的处理,和对象的原型(基类)的处理
-- 多重数组
-- lua 缺乏内置的表操作函数,得靠 for 自己展开
lua 中的函数可以接收多于,或者少于参数声明个数的参数。
function f(a, b) -- 函数名 f,参数 a,b
local c = a + b -- 局部变量
return c, a, b -- 返回值
end -- 函数结束
-- 位于最后参数的调用返回值,将会自动扩展
f(1, f(1,2)) -- f(1, 3, 1, 2) --> 4,1,3
可变长参数函数:
-- ... 是可变长参数表达式
function g(...) -- 可变长参数函数
local result = {}
-- 函数调用参数是列表或字符串时可以省略括号
for _,v in pairs{...} do -- {...} 转换为列表
table.insert(result, v) -- 将新元素插入表后端
end
return table.unpack(result) -- 将列表转化为可变长参数表达式
end
print( g(1,2,nil,3) ) -- 过滤掉 nil 元素
当函数内部最后是返回时调用其他函数,那么该函数的栈可以提前释放,因而不再占用栈空间。这叫尾调用优化。对于满足这种条件的递归函数来说,它将可以无限递归,而不会用尽栈空间。
function f(a)
if a > 1 then
return f(a-1) -- 尾递归
-- 注意必须在 return 之后,且没有任何操作
end
return a
end
f(1000000000.4) -- 不会栈溢出
-- io.stdin 默认输入流
-- io.stdout 默认输出流
-- io.stderr 默认错误流
file = io.read("a") -- 完整读取文件内容
-----------------
lines = {}
for line in io.lines() do -- 按行读取
table.insert(lines, line)
end
for k,line in ipairs(lines) do
-- 写入(可变长参数)
-- string.format 格式化
-- %0 填充0,-3宽度3,左对齐,d 整数
io.write(string.format("%0-3d", k),line,"\n")
end
------------------
-- file 流
-- err 失败错误
-- errnum 错误码
-- "r" 只读
filename = "/home/htqx/code/main.zig"
file, err, errnum = io.open(filename, "r")
if err then print(errnum,err) end
lines = file:read("a") -- 读取流
print(lines)
file:close() -- 关闭流
file = io.open(filename,"r")
io.input(file) -- 设置当前输入流
io.read() -- 读取一行
io.input(io.stdin) -- 设置回默认,标准输入流
file:close()
-------------------
file = io.popen("ls", "r") -- 执行命令并返回流
a:read()
file:close() -- 结束流和命令, 返回: true, "exit", 0
-- 分支语句
if not a then -- a == nil
a = 1
elseif a > 1 -- a ~= nil and a > 1
a = 0
else -- a ~= nil and a <= 1
a = 2
end
------------------
-- 循环语句
a = {1,2,nil,3}
local i = 1
while a[i] do
print(a[i]) -- 1,2
i = i + 1
end
local i = 1
repeat
print(a[i]) -- 1,2
i = i + 1
until not a[i] -- 为假时循环。且第一次总是运行的
for i=1, #a, 1 do -- 索引,目标值,步进
print(a[i])
i = i + 1
end
------------------
-- goto 语句
a = {0,-1,1,2,nil,3}
i = 1
while a[i] do
::redo:: -- 只能位于局部变量前
local j = i
i = i + 1
if a[j] == nil or a[j] == 0 then goto continue end
if a[j] < 0 then goto redo end
print(a[j])
::continue:: --最后一条除外
end
闭包有点类似匿名函数。在 lua 中,函数其实都是匿名的。它只是被赋予到一个同名的变量。
function f(x) return x*x end
f = function(x) return x*x end -- 等价
local function f(x) return f(x-1) end -- 局部函数
local f; f = function(x) return f(x-1) end; -- 等价
xim = {}
function xim.f(x) end
xim.f = function(x) end -- 等价
闭包:闭包主要特征是可以捕获外层函数的变量。lua 函数实际都是闭包。
function newCounter()
local count = 0
return function() -- 闭包
count = count + 1 -- 捕获外层变量 count
return count
end
end
c = newCounter() -- 被捕获的变量已经和 newCounter 无关,和 c 绑定
d = newCounter() -- 新闭包
print(c(),c(),c(),d()) -- 1,2,3,1
高阶函数:函数参数是函数
-- 导数公式: (f(x+d) - f(x)) / d, d 为无穷小
function g(f,d,...)-- 返回指定函数的导数
local x = {...}
d = d or 1e-4 -- 默认值
for i = 1, #x do
if not x[i] then goto continue end
x[i] = x[i] + d
::continue::
end
x = {f(table.unpack(x))}
local y = {f(...)}
for i = 1, #x do
if not x[i] or not y[i] then goto continue end
x[i] = (x[i] - y[i]) / d
::continue::
end
return table.unpack(x)
end
g(math.sin, nil, 5.2) -- 导数的近似值
math.cos(5.2) -- sin 的反函数是 cos
-----------------------
-- 对原函数参数的包装
function gen2(f,b)
return function(a,...)
return f(a,b,...)
end
end
gd = gen2(g, 1e-9) -- 对 g 进行包装,避免第二个参数 d 的使用
gd(math.sin, 5.2)
-----------------------
-- 柯里化:将多参数函数转为多个单参数函数
function currying(f,n,...)
local args = {...}
if n <= 0 then return f(...) end -- 调用原函数
return function(d)
table.insert(args, d) -- 添加到原参数后面
-- unpack 必须在最后才能扩展
return currying(f,n-1,table.unpack(args))
end
end
currying(math.min, 3)(5.2)(1)(-10) -- 柯里化
math.min(5.2, 1, -10)
有人说,编程的很大一部分内容,就是对字符串进行处理。确实,大部分编程语言都会内置强大的字符串处理函数。
模式字符:
.
: 点,代表任意字符%a
: 字母%c
: 控制字符%d
: 数字%g
: 非空格%l
: 小写字母%p
: 标点符号%s
: 空白%u
: 大写字母%w
: 字母和数字%x
: 十六进制数字%b()
: "(" 到 ")" 之间的字符,可替换为其他两个字符%f[]
: 前置约束集合以上大写为它的补集。
+
: 1 - n-
: 0 - n (最小匹配)*
: 0 - n?
: 0 - 1^
: 补集、开头$
: 结尾[]
: 集合
[0-7]
: 0 - 7 的字符集合()
: 匹配组
%0
: 整个匹配%1
: 第一组()
: 空捕获,返回位置%
: 转义以上基本是最大匹配。
str = "Nns110snN"
-- 单次和多次匹配的区别
string.match(str, "[01]+") -- 110
for v in string.gmatch(str, "[sn]+") do
print(v) -- ns,sn
end
-- 最小匹配和最大匹配的区别
string.match(str, "n.-s") -- ns
string.match(str, "n.*s") -- ns110s
-- 开头、结尾
string.match(str, "^.?.?") -- Nn
string.match(str, ".?.?$") -- nN
-- 范围匹配和前置匹配
string.match(str, "%bss") -- s110s
for v in string.gmatch(str, "%f[%l][ns]+%f[%u]") do
print(v) -- sn
end
前置匹配(Frontier pattern)的逻辑:当前字符满足[%l]
,且前一个字符不满足。这有点类似回顾查找否定式(lookbehind),即 (?<![])。看上去简单,实际使用起来还是有点绕的。
比如你想捕获 [ns]
, 先 [ns]+
,然后前面只要不是小写即可 %f[%l]
,否定式。后面是大写 %f[%u]
,肯定式。为什么?前面是否定形式的,因为当前字符是[ns]+
,后面是肯定式,因为当前字符是[ns]+
后了。这就是前置后置条件的写法差异。注意前中后的关系,必然是变化的,否则怎么提取呢?
前置匹配模式就是让你可以对前后的差异做一些限定,因为默认的限定只是[^ns]
。
str = "Nns110snN"
-- 匹配组,返回每组括号匹配的内容
string.match(str, "(%a+).-(%a+)") -- Nns,snN
-- %1 引用第一个匹配组的结果,即 n
string.match(str, "(%l)(.-%1)") -- n, s110sn
-- 格式化字符串
string.gsub(str, "(%a+)(.-)(%a+)", [[
<%1 size="%2">
<%3 />
</%1>]])
-- 替换为表对应项的值
string.gsub(str, "%a+", {Nns = 111, snN="000"}) -- 111110000
-- 用闭包处理结果
string.gsub(str, "%a+", function(s)return string.upper(s);end)
数据结构是算法的着力点,算法是问题的编程方法。方法高低(除了正确性)主要是看执行效率(空间和时间上的)。因此对于稍微复杂的问题来说,构造适当的数据结构是很关键的。
数据结构和算法的知识,是高于具体编程语言的,所以反过来用某个编程语言去实现经典数据结构和算法,对熟练这个语言也是极好的帮助。
数组遍历内层尽量对应低维度。如a[i][k]
对应 for i..for k..
。用 pair 代替索引,可以对稀疏数组进行性能优化。
字符串拼接符(..
)是低效率的,使用表({}
)来做缓冲,并使用 table.concat 来连接。
-- 单链表
list = {next = list, value = v}
-- 双端队列
queue = {first = 0, last = -1}
-- 反向表
days = {"一", "二", "三", "四", "五", "六", "日"}
revDays = {}
for k,v in pairs(days) do
revDays[v] = k
end
revDays["日"] -- 7
-- 图
node{name, adj = node}
graph["name"] = node
将语言内部的对象(结构和值),存储到文件中。序列化有多种编码方式。如二进制存储(高效),或文本存储(易读易改)。
现代语言都会包含模块。模块对程序的功能进行工程角度的逻辑划分。
-- 加载 mod.lua
-- 搜索路径 package.path
-- C 搜索路径 package.cpath
-- loadfile 加载
-- loadlib 加载 C 库
-- ./m.lua or ./m/init.lua
local m = require "mod"
定义一个模块:
-- package.loaded
-- package.preload
-- 子模块 M.b 的默认路径 m/init.lua m/b.lua
local M = {}
local function M.new(r,i)
return {r=r,i=i}
end
return M
迭代器本质是返回一个闭包。该闭包每次调用将返回下一个元素,配合 for...in
语句,就能循环直到返回空。
pairs 能返回一个表的迭代器。
next(t, i) 内置函数是 pairs 函数的关键,将返回 t 表 i 键的下一个元素 k,v(按内部固有顺序),如果 i 为 nil 将返回第一个元素。
-- 将表转换为迭代器
for k,v in pairs({...}) do
end
-- 实现一个范围迭代器
function rang(xend, start, step)
local start = start or 1 -- 提供默认值
local xend = xend or 1
local step = step or 1
local index = start -- 作为闭包状态
return function()
local i = index
index = index + step -- 修改捕获的变量
if i <= xend then
return i
end
end
end
-- for...in 获取闭包,调用闭包,直到它返回 nil,...
-- 一次最多只能保存接受三个闭包返回值
for i in rang(5) do
print(i)
end
-- 累计器,具有初始值和累计值
-- 该闭包没有绑定环境变量,是无状态(只用参数)迭代器
function sum(iter, start)
local start = start or 0
return function(s,var) -- 生成器、累计值
local next = s()
if not next then return end
return next + var -- 累计值
end, iter, 0 -- 函数、生成器、累计值
end
for s in sum(rang(5)) do
print(s)
end
元函数能实现对内置运算符和特殊函数的重载。
元表是原型的基础:当表找不到相关操作,就会从元表中查找原函数 __index
,从而获取在元表中定义的操作,因此元表就成了当前表的原型(类似 js 语言)。
原型实现了面向对象的继承操作,但元函数本身不会被继承。
__tostring
: tostring()__pairs
: pairs()__metatable
: 对元表进行保护__index
: 读取下标,即 self[k]
。只有不存在的下标时才会调用函数,如果要任意下标都调用,那么得用代理模式,将目标对象封装到内部。
__newindex
: 设置下标,即 self[k] = value
__len
: "#"__mode
: 弱引用。等于 "kv" 时键和值都是弱引用__gc
: 析构器-- 获取元表
-- 内置的字符串类型具备元表对象
getmetatable("")
---------------------
-- 设置元表
-- 元表本身也是普通的表
t = {v=1}
a = {} -- 元表
-- + 的元方法
a.__add = function(self, right) -- 添加元方法
local left = { v = self.v} -- 复制 self
setmetatable(left, getmetatable(self)) -- 含元表
left.v = left.v + right.v
return left
end
-- 转字符串的元方法
a.__tostring = function(self) return self.v end
setmetatable(t, a) -- 设置 t 元表为 a
print(t + {v=2}) -- 3
面向对象让对象可以使用相同的一组接口。lua 使用原型来实现面向对象。
原型条件:
setmetatable(t, a)
: 设置 a 为 t 原表a.__index = a
: 元表设置索引字段a = {}
aa = {__tostring = function()return "a{}" end}
setmetatable(a, aa)
function a:say() -- 相当于 a.say(self)
print(self) -- 隐藏的第一个参数 self
end
a:say() -- 相当于 a.say(a)
b,a = a,nil
b:say()
-- 原型(class)
c = {}
b.__index = b -- 设置 __index 为原始函数
setmetatable(c, b)
c:say() -- 继承了b(即 a 的更名)的接口
-- 但因为 b 没定义元函数 __tostring (在 aa 上,但元函数不能继承)
-- 所以输出默认的 table:0xnnnn...
function aa:hi()
local print = io.write
print(type(self),"{")
for k in pairs(self) do
print(k,", ")
end
print("}\n")
end
aa.__index = aa -- aa 变更为原型
c:hi() -- c --> b --> aa:hi()
协程是异步执行任务的技术。在业务活动中,很多时候需要我们同时执行多个任务。
协程(coroutine)互动关系:
resume 调用协程,yield 返回调用处,直到协程终结。
协程看上去像是一个交互式的麻烦一点的函数调用。但它能作为事件驱动、协作式多线程、构造式迭代器等目标的基础。
协程定义了四个状态:
co = coroutine.create(function(s) -- suspended
print("task1:", s) -- running
local r = coroutine.yield("2.do...") -- suspended
print(r) -- running
end) -- dead
print(type(co)) -- thread
_,v = coroutine.resume(co, "1.begin")
print(v) -- 2.do...
coroutine.resume(co, "3.end")
模式的意思是前人总结出来的设计经验。它不是硬性要求,但值得借鉴。
function producer(s)
for i=1, s do
send(i)
end
end
function consumer(task)
local value = receive(task)
while value do
print(value)
value = receive(task)
end
end
function send(x) -- 发送
return coroutine.yield(x) -- 消费驱动,因为它暂停了生产者
end
function receive(task) -- 接收
local _, value = coroutine.resume(task)
return value
end
-- 配置
prod = coroutine.create(function() return producer(10)end)
-- 启动
consumer(prod)
-------------------------------------
-- 管道过滤器
function filter(task)
return function() -- 返回闭包更方便
local value = receive(task) -- 既是消费者
while value do
send(value * value) -- 又是生产者
value = receive(task)
end
end
end
prod = coroutine.create(function() return producer(10)end)
filter = coroutine.create(filter(prod))
consumer(filter)
协程对于许多程序员来说是一个比较新的概念,所以有必要了解一下其中的细节。
协程保持状态,并允许暂停,当下次进入时,只能从暂停位置继续。
lua 的协程是非对称协程(存在调用和被调用者),对称协程两者是平等的。被调用者只会回到调用者(主协程)。
而对称协程会切换指定协程。只有单一操作 yield to xxx
,没有 yield - resume
成对操作。
对称协程的自由度,导致调用者和被调用者的角色不明确,也导致需要附加的手段来保持信息传递。因此非对称协程更易于编程。
lua 的协程是基于堆栈和第一类(first class)构造的完全协程(指没限制的)。
-- 生成式迭代器
function rangIter(s)
local co = coroutine.create(function() return producer(s)end)
return function()
return receive(co)
end
end
for i in rangIter(10) do
print(i)
end
-- 事件驱动
-- 典型的事件驱动框架,用队列来存储事件,用回调函数来处理事件
cmdQueue = {}
function read(stream, callback)
table.insert(cmdQueue, function() callback(stream:read()) end)
end
function write(stream, line, callback)
table.insert(cmdQueue, function() callback(stream:write(line)) end)
end
function stop()
table.insert(cmdQueue, "stop")
end
function runloop()
local nextCmd = table.remove(cmdQueue, 1)
while nextCmd do
if nextCmd == "stop" then break end
nextCmd()
nextCmd = table.remove(cmdQueue, 1)
end
end
-- 任务:读取多行文本,然后逆序输出
t = {}
function lines(line) -- 事件处理
if line then
t[#t + 1] = line
read(io.input(), lines)
else
index = #t
printlines() -- 读取完毕,产生输出事件
end
end
index = 0
function printlines()
if index < 1 then return print() end
write(io.output(), t[index].." ", printlines)
table.remove(t, index)
index = index - 1
end
read(io.input(), lines) -- 产生第一个事件
runloop() -- 执行事件循环
------------------------------------------------
-- 协程版事件驱动
function readline(stream)
local co = coroutine.running() -- 获取当前协程
local callback = function(l) coroutine.resume(co, l) end -- 恢复
read(stream, callback) -- 让主循环调用回调来恢复当前函数
local line = coroutine.yield() -- 获取stream:read()的值
return line
end
function writeline(stream, line)
local co = coroutine.running()
local callback = (function() coroutine.resume(co) end)
write(stream, line, callback)
coroutine.yield()
end
-- 业务代码看上去和同步一样,易于理解
function run()
local t = {}
local line = readline(io.input())
while line do
t[#t + 1] = line
line = readline(io.input())
end
for i = #t, 1, -1 do
writeline(io.output(), t[i].." ")
end
print()
stop()
end
co = coroutine.create(run)
coroutine.resume(co)
runloop()
事件驱动模型,如果按照传统的方法编程,会比较难理解。因为它让程序员基于”产生事件,回调事件处理函数"的思维来编程,有种认知负担在里面。而使用协程后,业务代码变得很清晰,和一般性的同步代码没有区别,细节都隐藏在背后了。
程序有点复杂,下面描述一下究竟干了什么:
协程本质就是就协作式多任务,resume - yield 相互配合将函数分隔成若干小任务,从而能穿插另一个任务。而系统现有的多任务方案(系统线程),是抢占式的,cpu 间隔到了,就轮转到另一个任务,任务之间顺序是随机的。
可能会有人对协作式产生疑问,因为它的顺序都是确定的,感觉不像是多任务一起干,感觉不像同时进行。这其实和任务本身的性质有关。
任务与任务之间如果是有先后顺序的,那么必然需要进行同步,就算线程环境也是要等待的。
如果任务之间没有先后顺序,那么就可以随机执行。那么能不能用协程实现这种随机执行的效果呢?
-- 协作式多任务
pool ={}
function with(...)
for _,v in pairs({...}) do
table.insert(pool, v)
end
end
-- 调度器
-- 改进的方法是 pool 装消费者,而不是随意重启生产者
function runPool()
while #pool > 0 do
local index = math.random(#pool)
local co = pool[index]
if coroutine.status(co) == "suspended" then
coroutine.resume(co)
else
table.remove(pool,index)
end
end
end
-- print range
function range(a,b)
local co = coroutine.create(function()
for i =a ,b do
print(i)
coroutine.yield() --如果用 coroutine.wrap 需要传参
end
end)
return co
end
with(range(1,2), range(3,6), range(7,10)) -- 无关任务
run = coroutine.wrap(runPool) -- 将协程包装成闭包
run()
以上代码运行良好,但是它其实也只是一个顺序被打乱的单线程程序。当某个节点被暂停,后续都需要等待(哪怕逻辑上他们是无关的任务)。要想实现真正的并行执行,必须借助系统的多线程。
但 lua 本身不支持多线程。原因是多线程太复杂,lua 只是一个基于标准 c 库设计的脚本语言。c 标准库没有定义线程。
虽然没有多线程,但是可以编写非阻塞的 api。
lua 作为嵌入式语言,它提供库嵌入到第三方使用(c),反过来 lua 也能调用 c 编写的库。
// 编译时添加 liblua.so 即参数 -llua
#include <stdio.h>
#include <string.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
void l_main(lua_State*);
int main(void){
char buff[256] = "print('hello lua based on c')"; // lua 源码
lua_State *lua = luaL_newstate(); // lua 状态机
luaL_openlibs(lua); // 打开标准库
luaL_loadstring(lua, buff); // 编译源码
lua_pcall(lua, 0, 0, 0); // 执行
//-------------
// 测试 lua 调用 c 函数
l_main(lua);
//-------------
fprintf(stderr, "%s\n", lua_tostring(lua, -1)); // 打印错误信息
lua_close(lua); // 关闭 lua
return 0;
}
//---------------------------------
// lua 调用 c 函数
int l_add(lua_State *);
void l_main(lua_State* lua) {
lua_pushcfunction(lua, l_add); // c 函数指针入 lua 栈
lua_setglobal(lua, "myadd"); // 绑定到全局变量 myadd
char buff[] = "print('myadd :', myadd(1,2))"; // 输出 3
luaL_loadstring(lua, buff);
lua_pcall(lua, 0,0,0);
}
long add(long a, long b) {
return a + b;
}
int l_add(lua_State *lua) { // 包装函数
long a = lua_tointeger(lua, 1); // 获取整数参数
long b = lua_tointeger(lua, 2);
long c = add(a,b);
lua_pushinteger(lua, c); // 返回值
return 1; // 返回值个数
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。