Linda¶
用于协调不同的程序,使它们进行协作。
提醒
在 Adapter >= 4.0 中可用。
介绍¶
CodeLab Adapter 4.0 内置了 Linda server(Tuple Space),目前我们提供了以下客户端(持续增加中...)与 Linda Tuple Space 交互:
Linda 最有趣的一个地方是,所有 Tuple Space 参与者(跨语言、跨系统、跨网络)都能够互操作,语义由参与者自己"协调", 所以 Alan Kay 将 Linda 称为"协调语言"。
基本操作(operate)¶
核心操作¶
- out: 生成一个元组(tuple) 到 元组空间(tuple space)
- in: 在tuple space中匹配元组,如果匹配到则消耗它, 如果未匹配则一直等待
- inp: in的非阻塞版本。 如果匹配到则消耗它, 如果未匹配则返回空元组
- rd: read only, 在tuple space中匹配元组,如果匹配到则返回它(不移除), 如果未匹配则一直等待
- rdp: 非阻塞版本的 rd
- eval: 暂不考虑实现
辅助操作¶
不在 linda 的原始论文中,是我自己的扩展
- dump: 获取元组空间所有元组
- status: 获取元组空间状态
- reboot: 重置元组空间
Python Client¶
安装依赖: pip install https://github.com/CodeLabClub/codelab_adapter_client_python/archive/master.zip
提供同步和异步两种基类:
- AdapterNode
- AdapterNodeAio
AdapterNode¶
from codelab_adapter_client import AdapterNode class MyNode(AdapterNode): NODE_ID = "linda/test" def __init__(self): super().__init__() node = MyNode() node.receive_loop_as_thread() time.sleep(0.1)
创建Adapter Node之后,就可以通过node使用linda了。
res = node.linda_reboot() # reboot linda server, clean tuple space assert res == [] res = node.linda_out([1, 2, 3]) # out assert res == [1, 2, 3] res = node.linda_out([1, 2, 4]) # out res = node.linda_dump() assert res == [[1, 2, 3], [1, 2, 4]] res = node.linda_rd([1, 2, 3]) # read and blocking assert res == [1, 2, 3] res = node.linda_rdp([1, 2, "*"]) # read but non-blocking assert res == [1, 2, 3] # 先入先出 res = node.linda_in([1,2,3]) # read then remove (blocking) assert res == [1, 2, 3]
AdapterNodeAio(异步)¶
同步和异步 API 保持一致
import asyncio from codelab_adapter_client import AdapterNodeAio class MyNode(AdapterNodeAio): NODE_ID = "linda/test" def __init__(self): super().__init__() # 以下代码在 jupyter 中运行,如果你想在python脚本中使用,请考虑异步代码的生命周期,参考: https://github.com/CodeLabClub/codelab_adapter_client_python/blob/master/tests/test_linda_client.py#L26 task = asyncio.create_task(node.receive_loop()) await asyncio.sleep(0.1) # !! 等待zmq通信管道建立完成 _tuple = ["test_linda"] # reboot res = await node.linda_reboot() assert res == [] # out _tuple = ["hello", "world"] await node.linda_out(_tuple) # rdp res = await node.linda_rdp(_tuple) assert res == _tuple # inp res = await node.linda_inp(_tuple) assert res == _tuple res = await node.linda_dump() assert res == []
更多用法参考测试文件: test_linda_client.py
Scratch Client¶
REST API¶
使用 httpie 作为客户端。:=
表示后边跟的是 json 数据
out¶
http post https://codelab-adapter.codelab.club:12358/api/linda operate=out tuple:='["hello", "linda"]'
in¶
http post https://codelab-adapter.codelab.club:12358/api/linda operate=in tuple:='["hello", "linda"]'
dump¶
http post https://codelab-adapter.codelab.club:12358/api/linda operate=dump
其他原语类似
cli (命令行客户端)¶
pip install https://github.com/CodeLabClub/codelab_adapter_client_python/archive/master.zip # pip install codelab_adapter_client --upgrade # 暂未更新到 pypi codelab-linda --help codelab-linda out --help # reboot codelab-linda reboot # dump codelab-linda dump # out codelab-linda out --data '[1, "hello"]' # rd codelab-linda rd --data '[1, "hello"]' codelab-linda rd --data '[1, "*"]' # rdp codelab-linda rd --data '[1, 2, 3]' # [] # in codelab-linda rd --data '[1, "*"]'
JavaScript Client¶
方便开发者,将 Linda 引入自己的web项目。
CodeLab 目前使用 JavaScript Client,将 Linda 带入 CodeLab Scratch、CodeLab Adapter WebUI 和 Lively。
import AdapterBaseClient from "./codelab_adapter_base.js"; // https://github.com/CodeLabClub/scratch3_eim/blob/v3/codelab_adapter_base.js let NODE_ID = "linda/js/client"; let HELP_URL = "https://adapter.codelab.club/user_guide/Linda/"; let runtime = null; let adapter_client = new AdapterBaseClient(NODE_ID, HELP_URL, runtime); await adapter_client.linda_out([1,2,3]).then((data)=>{console.log("linda",data); return data}) tuple = await adapter_client.linda_in(["hi", "lively", "*"]).then((data)=>{console.log("linda",data); return data}) tuple = await adapter_client.linda_in(["hi", "python", "from Lively"]).then((data)=>{console.log("linda",data); return data}) tuple = await adapter_client.linda_in(["hello", "lively", "*"]).then((data)=>{console.log("linda",data); return data}) await adapter_client.linda_in([1,2,5], 1000).then((data)=>{console.log("linda",data); return data}) //超时
mush-lang¶
LISP 是一种构建材料 -- Alan Kay
为了更好地探索 Linda 的可能性,我们围绕 Linda 的基本原语,构建了一门简单的语言 -- mush-lang。
mush-lang 采用 LISP 风格的语法,可以视为 LISP 的一门玩具方言。 LISP 因其同构性(内外表示一致),可能是所有语言中最简单的。
mush-lang 目前在 Python 中实现。
Demo¶
多个 Scratch 角色 的 实时同步¶
在 Python 的例子中,我们甚至在Scratch里构建了 Server!
两个Scratch角色同步的代码如下
python 与 Scratch 同步的代码如下:
Python核心部分代码为:
node.linda_out(["request", "loudness", "xxx"]) node.linda_in(["response", "loudness", "*"])
Jupyter 与 Scratch 的互操作¶
跨语言对象之间的互操作
用到了 Jupyter notebook 里的 ipywidgets.
# 请使用 jupyter notebook,而不是jupyterlab from ipywidgets import interact, interactive, fixed, interact_manual from codelab_adapter_client import AdapterNode import time class MyNode(AdapterNode): NODE_ID = "linda/jupyter" def __init__(self): super().__init__() node = MyNode() node.receive_loop_as_thread() @interact(show=True, x=100, size=100) def f(show,x,size): node.linda_out(["%%x", x], wait=False) # f函数是非阻塞的回调函数,使用wait=False参数,使node.linda_out使非阻塞的,此时相当于流,记得使用 message tuple(见下文) node.linda_out(["%%show", show], wait=False) node.linda_out(["%%size", size], wait=False) return show,x,size
进阶¶
消息风格¶
linda 的基本观点是数据不停生灭(由用户显式操控)。
如果我们想在 Linda 中实现 "消息/流" 的模式,可能会遇到tuple堆积(生产者太快)的问题(这是很严重的问题,似乎也不是正确使用linda的方式)
为了尽可能少地破坏概念完整性,我们引入了一种特殊的tuple来支持"消息/流"模式。
我们定义了一种叫做 message tuple 的 tuple,它像消息一样,每次只能流的瞬时截面: 一个数据。
以下是几个message tuple的例子:
- ("%%x", 1)
- ("%%y", 50)
- ("%%z", "hello", "world")
在语法层面,message tuple只是普通的tuple,唯一区别是第一个元素需要是如下风格字符串, "%%x", x可以是任意值,可以把它看作message tuple的id,不同id的message tuple被视为不同tuple,支持tuple的所有操作符。
以下是一个例子:
视角
站在变量的视角,你可以将其看作全局变量
FAQ¶
如何看到 Linda Tuple Space¶
Adapter >=4.1.0
在 Scratch 里有些 in/rd 积木一直阻塞¶
简单而言,按照以下顺序运行程序:
- 确保在linda in/rd 积木运行之前,先运行linda reboot
- 之后在启动Scratch程序
以下是原因分析(可以不看):
这个Linda背后的实现有关,Adapter Linda 目前是C/S架构。Scratch中的 in/rd 积木实际上 promise。
reboot针对的是linda server的操作。
如果程序在 in/rd 的时候,被reboot,则客户端(Scratch)的 in/rd 对应的promise永远不会被解决。
linda reboot 一下
速度¶
<!-- ZMQ_LOOP_TIME
以下是一个在scratch里进行速率测试的demo
默认情况下,30帧/s。
在Python客户端,通过修改参数,可以提高到300-600帧/s。
class MyNode(AdapterNode): NODE_ID = "linda/test" def __init__(self): super().__init__(recv_mode="block", bucket_fill_rate=1000, bucket_token=1000)
Linda 与 EIM¶
Linda 与 EIM 将长期共存,一个 Adapter Node,即是Linda client,也是EIM client,它们各有所擅。长期来看,我们更偏好 Linda。