`
linkerlin
  • 浏览: 34904 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

[旧文重发]ConcurrentLua--面向并发的Lua编程

阅读更多

ConcurrentLua--面向并发的Lua编程

原文地址
Linker 翻译此文只为提供更多信息.

介绍

ConcurrentLua 是一个无共享异步消息传递模型的实现.该模型来自Erlang语言.
她改编了Erlang的并发元素并整合进Lua里.

ConcurrentLua的一个核心元素是 process(进程).一个进程是一个轻量级虚拟机
线程,扮演和操作系统的进程同样的角色;他们不共享内存而是使用某这进程间通讯
机制.这些进程能够根据需要被创建和销毁,并通过一个简单的环罗宾(轮训)算法来
调度他们.

每一个进程关联到一个邮箱(临时存储消息的队列),通过邮箱来接受别的进程发来
的消息.进程可以在任何时候检查自己的邮箱有没有新消息抵达,如果有,进程可以
按照抵达的顺序依次读取.

每个进程都有一个唯一的数字作为进程标识,叫 PID(process identifier).也可以
给进程取一个名字,并用名字来指代进程.进程名和进程的对应关系被保存到一个
中心储藏室--registry(注册表).进程可以编辑注册表,添加或者删除表项.

错误捕捉机制也被实现成 monitors 和 links .通过 monitors 进程能够监视其他
进程,并在被监视进程异常终止的时候获得通知.通过 linker 进程绑定进程到一起,
当一个进程异常终止的时候,其他进程也被通知并终止.

本系统还支持分布式编程和所有相关的组件.分布式的进程通过和本地进程一样的
方式来通讯.

分布是基于 node(节点) 组件的.一个节点代表一个运行着很多进程的运行时环境.
节点们可以相互连接和通讯,于是建立了一个虚拟网络.分布的进程使用这个网络来
顺序交换信息.

每个节点有个名字.其他的节点可以通过这个名字来连接.一个端口映射器精灵进程
(译注:就是服务进程,类似Erlang提供的名字服务)提供了名字解析服务.端口映射器
知晓虚拟网络中所有的节点的信息.

正如进程可以在本地创建,进程已可以在远端节点被创建.一个远程进程能够被视同
为一个本地进程来操作.

如果虚拟网络中的节点是全互联的(每一个节点双向连接其他的节点),那么可以使用
全局进程名.节点们相互交流和保养虚拟全局注册表并保持自己本地的注册表时时
更新.

monitors 和 links 以同样的语义支持分布进程和本地进程.节点可以透明的处理
分布进程的错误.另外,进程可以像监视整个节点.

节点可以在通讯前进行鉴权.一个已鉴权的节点才可以成为虚拟网络的一部分.这些
策略通过一个简单安全机制来保证.

实现

ConcurrentLua的实现是基于Lua组件系统实现的.这个系统负责组织和管理Lua
的模块和子模块.主模块有两个,分别提供了并发功能和分布式编程功能.并发模块
可以单独加载,每个模块都可以选择性加载需要使用的子模块.独立的端口映射器
精灵进程也是实现的一部分.

系统中的进程是通过Lua的协程机制来实现的.一个进程其实就是一个Lua协程,
通过 yield 来挂起一个进程,通过 resume 来继续执行一个进程.

进程的调度机制仍然是基于Lua使用的 协作式多线程模型. 进程自愿挂起自己,
从而让其它的进程获得运行的机会.然而,挂起和恢复进程被部分隐藏于高层机制
之下;当一个进程去等待消息抵达的时候挂起,而在消息抵达进程邮箱后准备恢复.
一个简单的环罗宾(轮训)调度器用来恢复进程的执行.

任何类型的Lua数据,除了内存引用外,都可以通过消息来发送.消息可以是布尔值,
数字,字符串,表或者函数,或者他们的混合.数据自动在发送时被序列化,并在接受
时反序列化,所以的数据都是值传递.

节点间的分布式进程间通讯机制是基于异步socket的.映射到网络层是非阻塞
socket和定时轮训.这是如今大部分Lua模块采用的方法,非阻塞语义也应该被用
在例如文件和管道的IO操作上.


用法

一些例子提供了系统不要组件的用法,例如,创建进程,分布式进程的消息传递和错误捕获.

创建进程

spawn()函数可以创建进程.spawn()函数接受至少一个参数,该参数标志进程的入口函数.
其它附加参数则被直接转交给入口函数.

下面的例子示范创建一个进程.该进程输出指定次数的消息:
require 'concurrent'

function hello_world(times)
    for i = 1, times do print('hello world') end
    print('done')
end

concurrent.spawn(hello_world, 3)

concurrent.loop()


输出应该是:
hello world
hello world
hello world
done


首先加载系统:
require 'concurrent'
进程入口函数:
function hello_world(times)
    for i = 1, times do print('hello world') end
    print('done')
end

创建一个新进程:
concurrent.spawn(hello_world, 3)
最后调用系统无限循环:
concurrent.loop()

消息交互

进程通过 send() 和 receive() 函数来交换消息.同样,self()函数也被用来获取本进程ID.
下面的程序实现了两个进程交换消息然后终止:
require 'concurrent'

function pong()
    while true do
        local msg = concurrent.receive()
        if msg.body == 'finished' then
            break
        elseif msg.body == 'ping' then
            print('pong received ping')
            concurrent.send(msg.from, { body = 'pong' })
        end
    end
    print('pong finished')
end

function ping(n, pid)
    for i = 1, n do
        concurrent.send(pid, {
            from = concurrent.self(),
            body = 'ping'
        })
        local msg = concurrent.receive()
        if msg.body == 'pong' then
            print('ping received pong')
        end
    end
    concurrent.send(pid, {
        from = concurrent.self(),
        body = 'finished'
    })
    print('ping finished')
end

pid = concurrent.spawn(pong)
concurrent.spawn(ping, 3, pid)

concurrent.loop()


输出应该是:
pong received ping
ping received pong
pong received ping
ping received pong
pong received ping
ping received pong
pong finished
ping finished


在 pong 进程被创建后, ping 进程获得了 pong 进程的 PID:
pid = concurrent.spawn(pong)
concurrent.spawn(ping, 3, pid)


ping 进程发送一个消息:
concurrent.send(pid, {
    from = concurrent.self(),
    body = 'ping'
})


pong 进程等待消息抵达,然后把接收到的消息保存到一个变量中:
local msg = concurrent.receive()

pong 进程回复:
concurrent.send(msg.from, { body = 'pong' })

pong 进程在接收到 ping 进程发来的一个提示后终结.

注册进程名

可以用进程名替代PID来指定消息接收方. register() 函数可以用来在注册表
(译注:指系统的名字对应表,而不是Windows的注册表,顺便鄙视一下Windows. :) )
创建一个进程的名字:
require 'concurrent'

function pong()
    while true do
        local msg = concurrent.receive()
        if msg.body == 'finished' then
            break
        elseif msg.body == 'ping' then
            print('pong received ping')
            concurrent.send(msg.from, { body = 'pong' })
        end
    end
    print('pong finished')
end

function ping(n)
    for i = 1, n do
        concurrent.send('pong', {
            from = concurrent.self(),
            body = 'ping'
        })
        local msg = concurrent.receive()
        if msg.body == 'pong' then
            print('ping received pong')
        end
    end
    concurrent.send('pong', {
        from = concurrent.self(),
        body = 'finished'
    })
    print('ping finished')
end

pid = concurrent.spawn(pong)
concurrent.register('pong', pid)
concurrent.spawn(ping, 3)

concurrent.loop()


相对前一个版本的改变就是 ping 进程发送消息的地方:
concurrent.send('pong', {
    from = concurrent.self(),
    body = 'ping'
})

和:
concurrent.send('pong', {
    from = concurrent.self(),
    body = 'finished'
})


以及现在 pong 进程注册了它的名字:
concurrent.register('pong', pid)

因此 ping 进程不需要知道 pong 进程的 PID 了.

分布式消息传递

不同节点上的进程仍然可以使用同样的消息传递机制.远程进程通过 PID或进程名 加上
节点名来指定.先前的例子可以改造成两个程序,分别是一个独立进程.

pong 进程的代码如下:
require 'concurrent'

function pong()
    while true do
        local msg = concurrent.receive()
        if msg.body == 'finished' then
            break
        elseif msg.body == 'ping' then
            print('pong received ping')
            concurrent.send(msg.from, { body = 'pong' })
        end
    end
    print('pong finished')
end

concurrent.init('pong@gaia')

pid = concurrent.spawn(pong)

concurrent.register('pong', pid)
concurrent.loop()
concurrent.shutdown()


ping 进程的代码如下:
require 'concurrent'

function ping(n)
    for i = 1, n do
        concurrent.send({ 'pong', 'pong@gaia' }, {
            from = { concurrent.self(), concurrent.node() },
            body = 'ping'
        })
        local msg = concurrent.receive()
        if msg.body == 'pong' then
            print('ping received pong')
        end
    end
    concurrent.send({ 'pong', 'pong@gaia' }, {
        from = { concurrent.self(), concurrent.node() },
        body = 'finished'
    })
    print('ping finished')
end

concurrent.spawn(ping, 3)

concurrent.init('ping@selene')
concurrent.loop()
concurrent.shutdown()


(译注: 如果你想自己跑这个例子需要修改上面的节点名后半部分的机器名部分,使之和你的网络环境相匹配.)

pong 进程的输出应该是:
pong received ping
pong received ping
pong received ping
pong finished


ping 进程的输出应该是:
ping received pong
ping received pong
ping received pong
ping finished


在这个例子里,运行时系统运行在分布式模式.为了看到结果,端口映射器必须先运行:
$ clpmd
初始化 pong 进程所在节点的代码:
concurrent.init('pong@gaia')
初始化 ping 进程所在节点的代码:
concurrent.init('ping@selene')

上面两句代码注册节点到端口映射器.去注册是通过:
concurrent.shutdown()

这个例子的唯一改动是消息发送的目的地.node()函数会返回调用进程坐在节点的名字:
concurrent.send({ 'pong', 'pong@gaia' }, {
    from = { concurrent.self(), concurrent.node() },
    body = 'ping'
})


接下来:
concurrent.send({ 'pong', 'pong@gaia' }, {
    from = { concurrent.self(), concurrent.node() },
    body = 'finished'
})


错误处理

一个捕获进程间错误的方法是连接进程.两个进程被绑定到一起,一个异常终止的后
另一个也会终止.link()函数用来绑定进程:
require 'concurrent'

function ping(n, pid)
    concurrent.link(pid)
    for i = 1, n do
        concurrent.send(pid, {
            from = concurrent.self(),
            body = 'ping'
        })
        local msg = concurrent.receive()
        if msg.body == 'pong' then
            print('ping received pong')
        end
    end
    print('ping finished')
    concurrent.exit('finished')
end

function pong()
    while true do
        local msg = concurrent.receive()
        if msg.body == 'ping' then
            print('pong received ping')
            concurrent.send(msg.from, { body = 'pong' })
        end
    end
    print('pong finished')
end

pid = concurrent.spawn(pong)
concurrent.spawn(ping, 3, pid)

concurrent.loop()


输出应该是:
pong received ping
ping received pong
pong received ping
ping received pong
pong received ping
ping received pong
pong finished -- 译注:这里应该是: ping fininshed


pong 进程永远不会运行到最后一行,因为他在接收到 ping 进程退出信号的时候会终止.

连接进程的代码如下:
concurrent.link(pid)

也可以捕获进程终止导致的exit信号.被捕获的exit信号会转换成一个特殊的消息:
require 'concurrent'

concurrent.setoption('trapexit', true)

function pong()
    while true do
        local msg = concurrent.receive()
        if msg.signal == 'EXIT' then
            break
        elseif msg.body == 'ping' then
            print('pong received ping')
            concurrent.send(msg.from, { body = 'pong' })
        end
    end
    print('pong finished')
end

function ping(n, pid)
    concurrent.link(pid)
    for i = 1, n do
        concurrent.send(pid, {
            from = concurrent.self(),
            body = 'ping'
        })
        local msg = concurrent.receive()
        if msg.body == 'pong' then
            print('ping received pong')
        end
    end
    print('ping finished')
    concurrent.exit('finished')
end

pid = concurrent.spawn(pong)
concurrent.spawn(ping, 3, pid)

concurrent.loop()


输出应该是:
pong received ping
ping received pong
pong received ping
ping received pong
pong received ping
ping received pong
pong finished
ping finished


可以通过 setoption() 函数来设置进程链接的选项,这里是 trapexit 选项:
concurrent.setoption('trapexit', true)

pong 进程会接收到一个退出消息:
if msg.signal == 'EXIT' then
    break


基于提示消息的monitor, 也可以用来处理错误.































































分享到:
评论
1 楼 linkerlin 2010-04-09  
 

相关推荐

    所有版本LUA源码

    lua-5.3.5 lua-5.3.4 lua-5.3.3 lua-5.3.2 lua-5.3.1 lua-5.3.0 lua-5.2.4 lua-5.2.3 lua-5.2.2 lua-5.2.1 lua-5.2.0 lua-5.1.5 lua-5.1.4 lua-5.1.3 lua-5.1.2 lua-5.1.1 lua-5.1 lua-5.0.3 lua-5.0.2 lua-5.0.1 ...

    obs-文本-脚本 date-and-time.lua

    obs-文本-脚本 date-and-time.lua

    vscode-coco2dx-lua-api.7z

    《使用VSCode高效开发Cocos2d-x Lua项目——基于"vscode-coco2dx-lua-api.7z"的工具解析与应用》 在现代游戏开发领域,Cocos2d-x作为一款广泛使用的跨平台游戏引擎,为开发者提供了丰富的功能和高效的性能。而在...

    v1-scriptbywave-0-20-2-lua.lua

    v1-scriptbywave-0-20-2-lua.lua

    Lua中文参考----学习Lua的好资料

    元表则是Lua中实现面向对象编程的关键,通过元表,可以定义对象的行为和继承关系。 3. **函数和闭包**:Lua中的函数是第一类公民,可以作为参数传递、作为返回值返回。闭包则允许函数记住并访问其词法作用域内的...

    云风-lua源码欣赏-lua-5.21

    总之,《云风-lua源码欣赏-lua-5.21》是一本深入探索Lua源码的宝贵资料,对于想要理解Lua底层机制、提升编程技巧或者进行Lua扩展开发的读者来说,具有极高的学习价值。通过阅读这本书,读者能够掌握Lua的核心技术,...

    lua-language-server:Lua编码的Lua语言服务器

    Lua语言服务器(lua-language-server)是一个由Lua编程语言编写的开源项目,专门用于提供Lua代码的智能感知和增强编辑体验。它实现了Language Server Protocol(LSP),这是一种通用协议,允许文本编辑器和IDE与语言...

    STEP-BY-STEP--LUA.zip_lua_lua脚本_step by step

    6. **元表和元方法**:Lua允许通过元表和元方法来定制对象的行为,实现面向对象编程的特性。 7. **垃圾回收**:Lua采用自动垃圾回收机制,程序员无需手动管理内存。 8. **字符串处理**:Lua提供了丰富的字符串操作...

    protoc-gen-lua-master proto生成lua

    标题中的"protoc-gen-lua-master proto生成lua"涉及到的是一个使用ProtoBuf(Protocol Buffers)与Lua结合的工具,具体来说,`protoc-gen-lua`是一个代码生成器,它扩展了Google的`protoc`编译器,用于将.proto文件...

    使用_cocos2d-x_和_Lua_快速开发游戏.pdf

    该文不仅阐述了cocos2d-x与Lua的优势,还提供了一系列实用的开发技巧和建议。 #### 二、cocos2d-x与Lua简介 - **cocos2d-x**:一款跨平台的游戏开发框架,支持iOS、Android、Windows等多平台。其核心由C++编写,...

    lua-devel-5.3.4-12.el8.aarch64

    在这个特定的案例中,我们关注的是 `lua-devel-5.3.4-12.el8.aarch64`,这是一个针对 CentOS 8 操作系统、aarch64(64位 ARM 架构)平台的 Lua 5.3.4 开发版本。 首先,版本号 `5.3.4` 表示这是 Lua 5.3 系列的第四...

    lua-resty-websocket, 对ngx_lua模块( 和 OpenResty )的web socket支持.zip

    lua-resty-websocket, 对ngx_lua模块( 和 OpenResty )的web socket支持 电子邮件名称lua-resty-websocket - ngx_lua模块的Lua web socket实现 table-内容名称状态描述概要说明模块resty.websocket.server方法新插件...

    魔兽世界插件-LUA编辑器 WOWLUA

    WowLua 是一个在魔兽世界里边运行 Lua 脚本的编辑工具环境,他功能包括: 交互式 Lua 解释器 多页脚本编辑器。 语法着色 输出重定向到 WowLua 的输出窗口 WowLua 的输出窗口 /wowlua 或 /lua 打开 WowLua。 /...

    Lua中文编辑器luaEditor

    LuaEditor是一款专为Lua编程设计的中文编辑器,它为程序员提供了方便的开发环境,提高了编写和调试Lua代码的效率。luaEditor-v4.10是该编辑器的一个版本,据描述显示,这个版本在用户体验上得到了用户的认可。 对于...

    luajava-1.1-x64-lua51

    《 luajava-1.1-x64-lua51:Lua与Java的桥梁》 在IT领域,尤其是在游戏开发和脚本编程中,Lua和Java两种语言常常被结合使用,以发挥各自的优势。"luajava-1.1-x64-lua51"是一个针对64位系统的版本,它提供了将Lua...

    EmmyLua-AttachDebugger 可用

    EmmyLua-AttachDebugger 是一个专门针对Unity游戏引擎的lua脚本调试工具,它使得开发者能够在Unity集成开发环境(IDE)如IntelliJ IDEA和Rider 2020及更高版本中对lua代码进行断点调试。这个工具极大地提高了lua编程...

    32位,64位的 luajava-1.1-x64-lua51.zip

    在信息技术领域,跨语言交互是常见且重要的需求, Lua 和 Java 作为两种广泛应用的编程语言,通过 luajava 框架实现了高效的数据交换和功能调用。本文将深入探讨 luajava-1.1-x64-lua51.zip 压缩包中的内容,以及...

    LuaDelphi2010-v1.3(修改lua,支持中文函数,支持delphi xe2)

    Lua, LuaLib; type TMyLua = class(TLua) published function HelloWorld(LuaState: TLuaState): Integer; end; function TMyLua.HelloWorld(LuaState: TLuaState): Integer; var ArgCount: Integer; I: ...

    Cocos2d-x实战++Lua卷.pdf

    根据提供的文件信息,本文将重点围绕“Cocos2d-x实战++Lua卷”这一主题进行深入探讨,并结合描述部分给出的知识点,详细阐述Cocos2d-x与Lua在游戏开发中的应用。 ### Cocos2d-x简介 Cocos2d-x是一款开源的游戏引擎...

    skynet-crypty_windows(lua5.1)

    在云风的Skynet源码基础上,我们可能需要修改lua-crypt.c文件,以适应Windows的API调用和数据类型。这可能包括更改文件I/O操作、内存管理等部分。编译完成后,生成的动态链接库(.dll文件)将与项目链接,为Lua脚本...

Global site tag (gtag.js) - Google Analytics