惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
Latest news
Latest news
P
Palo Alto Networks Blog
T
The Exploit Database - CXSecurity.com
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
AI
AI
阮一峰的网络日志
阮一峰的网络日志
量子位
Project Zero
Project Zero
Know Your Adversary
Know Your Adversary
Google Online Security Blog
Google Online Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Schneier on Security
大猫的无限游戏
大猫的无限游戏
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
K
Kaspersky official blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
Visual Studio Blog
Spread Privacy
Spread Privacy
C
CERT Recently Published Vulnerability Notes
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
W
WeLiveSecurity
博客园 - Franky
Last Week in AI
Last Week in AI
博客园 - 聂微东
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
IT之家
IT之家
I
Intezer
The Cloudflare Blog
月光博客
月光博客
N
News | PayPal Newsroom
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
V2EX
博客园_首页
罗磊的独立博客
NISL@THU
NISL@THU
爱范儿
爱范儿
Jina AI
Jina AI
有赞技术团队
有赞技术团队
The Hacker News
The Hacker News
A
Arctic Wolf
Scott Helme
Scott Helme
Engineering at Meta
Engineering at Meta
P
Proofpoint News Feed

夜行人

回家路上 第一期的直播演示项目 震动检测器 正能量 在线参观CodeLab Neverland 发布 CodeLab Adapter 3.3.1 DynamicTable 之 纸糊方向盘 CodeLab DynamicTable: 一个可实施的技术方案 CodeLab Insight 发布 Alpha 版 情人节 Home Assistant 周报 && IoT 周报 (02) Joplin: 关注隐私的 Evernote 开源替代软件 浏览器的未来与 Web 传感器 Home Assistant 周报 && IoT 周报 (01) 百宝箱(01) 论自由 介绍 WebThings Home Assistant 周报 && iot 周报 (00) 百宝箱(00) 毛姆读书心得 传世之作 周末徒步 CodeLab Adapter ❤️ Jupyter/Python 航班 躲雨 夏令营途中 [译]思想--作为一种技术 The future of coding 美国之行 三门问题的程序模拟 从Python转向Pharo https://blog.just4fun.site/post/iot/iot-open-source-projects/ Python异步编程笔记 https://blog.just4fun.site/post/iot/iot-open-source-hardware-community/ 万物积木化开发者社区 CodeLab ❤️ Blender Scratch3技术分析之云变量 API(第7篇) [译]对管道(Pipes)的偏爱 [译]提出正确的问题比得到正确答案更重要 蓝牙设备与Scratch3.0 创建你的第一个Scratch3.0 Extension Scratch3技术分析之项目内部数据(第6篇) Scratch3技术分析之社区 API(第5篇) Scratch3技术分析之User API(第4篇) Scratch3技术分析之项目主页API(第3篇) Scratch3技术分析之静态资源API(第2篇) Scratch3.0、micro:bit与Windows7 https://blog.just4fun.site/post/iot/zerynth-vs-micropython/ 核聚变、方所与半宅空间 可视化编程为何是个糟糕的主意 codelab.club周末聚会 关于codelab.club '下一件大事'是一个房间 Hungry Robot - Eat everything 编程作为一种思考方式 今日简史 史蒂夫·乔布斯传 罗素自选文集 https://blog.just4fun.site/post/edx/tianjin-scratch-ai/ https://blog.just4fun.site/post/edx/richie-cms-openedx/ 徒步武功山 WebUSB与micro:bit 积木化编程与3D场景 夜宿武功山顶 scratch3-adapter接入优必选Alpha系列机器人 https://blog.just4fun.site/post/edx/video-migration-note/ scratch3-adapter重构笔记 https://blog.just4fun.site/post/edx/edx-community-members/ 两种硬件编程风格的比较 使用micro:bit自制PPT翻页笔 柏拉图对话集 scratch3.0 + micro:bit 七月电影放映计划 非营利组织的管理 Screenly--用树莓派让任何屏幕变为可编程的数字标牌 以最佳实践开始你的Django项目 micro:bit与事件驱动 为Scratch3.0设计的插件系统(上篇) OCR应用一例 近两年读过的一些好书 blockly开发之使用python驱动浏览器中的turtle(2) 牛顿新传 文学理论入门 逻辑的引擎 人生的意义 blockly开发之生成并运行js代码(1) blockly开发之hello world(0) micro:bit使用笔记 神器之Termux https://blog.just4fun.site/post/iot/micropython-notes/ Cozmo what is this Scratch的前世今生 下段旅程 我行在远方 爆裂 途中杂记 https://blog.just4fun.site/post/edx/open-edx-startup/ cozmo系列之入门 - 有性格且可编程的机器人 PaperWeekly开发笔记 创业二三事
建立在异步消息之上的同步指令: 分别在JavaScript、Python、Squeak、Snap! 上实现
种瓜 · 2020-11-17 · via 夜行人

前言

2024-04-03更新: Dynatalk 实现了本文中的想法

在 CodeLab Scratch 有这样一个需求: Scratch 积木执行同步语义,积木背后的通信是采用异步的 pub/sub。

解决这件事的关键代码我们在下边有列出。

本文试图在 JavaScript 之外的语言中也实现这种想法。它看起来只是一个简易的 RPC client(实现上,确实也是参考了Scratch jsonrpc),如果你想寻找 RPC 库,市面上有大把实现,本文不值一读。

如果你也对消息感兴趣,对于按照自己的想法构建事物感兴趣,而不是沉迷于各类模式和技巧,那么可将本文视为海滩上的一粒鹅软石,它并不珍贵,也不精美,它只是希望引起你的兴趣:引起你对打磨它的那些潮水、沙粒、软石,甚至大海的兴趣。

当然它也是我自己的一个临时 playground/workspace,作为编程技能的一个小练习,在 JavaScript/Python/Squeak 之间切换,以熟悉它们的异同点。

请记住,我们的 view point 在消息上。在这种视角上,RPC 是利用消息的一种形式,如此一来,这种经典模式并不存在固态的结构,它只是对消息的一种看法。

思路

思路是这样的,执行同步指令的时候,发送(pub)一个指令出去(在远端执行),接着返回一个 Promise,直到 Promise 完成之后,指令才往下执行,如此一来,就完成了同步的语义。

那么如何知道 Promise 是否完成呢?

将这些 Promise 采集在一起,每当收到(sub)远端发来的消息(有些消息是发出去的指令运行的结果),就去解决对应的 Promise。

如何知道收到的消息与 Promise 的对应关系呢?

使用 message id!

我们首先在 JavaScript(CodeLab Scratch) 里实现了 建立在异步消息之上的同步指令

我们构建了一个 js object: this._promises = {}, 用于存放指令 Promise 的 resolve。

并且设置了一个超时时间,如果超过这个时间,Promise 还没有被解决,则打印错误日志。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    get_reply_message(messageID, timeout=5000) {
        return new Promise((resolve, reject) => {
            this._promises[messageID] = resolve;
            setTimeout(() => {
                if (this._promises[messageID]){
                    console.error(`timeout(${timeout}ms)`)
                }
            }, timeout);
        });
    }

在每次收到 Adapter 的消息的时候,都检查下是否有对应的 Promise,如果有,就解决它。

1
2
3
4
5
if (typeof message_id !== "undefined") {
	this._promises[message_id] &&
		this._promises[message_id](content) &&
		delete this._promises[message_id];
}

完整的源码细节,参考:codelab_adapter_base.js

Python

Promise 在 Python 里的对应物是 Future。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import asyncio
import uuid
_futures = {}

async def in_coming_message(msg):
    '''
    -> message
    msg
        content
        message_id
    in a task
    '''
    inCommingMessageId =  msg['message_id']
    future = _futures.get(inCommingMessageId)
    if future:
        future.set_result(f"{msg['content']}-{msg['message_id']}")


async def pub_cmd(message_id):
    # message_id = uuid.uuid4().hex
    future = asyncio.Future()
    _futures[message_id] = future
    # todo timeout
    return await future

async def mock_sub():
    '''
    模拟sub接收消息
    '''
    await asyncio.sleep(2)
    for i in [1, 2, 3]:
        msg = {}
        msg['content'] = "reply"
        msg["message_id"] = i
        await in_coming_message(msg)
        await asyncio.sleep(1)

async def main():
    # loop = asyncio.get_running_loop()
    asyncio.create_task(mock_sub())
    print(await pub_cmd(1))
    print(await pub_cmd(2))
    print(await pub_cmd(3))


asyncio.run(main())

可以看出,要实现我们的目标: 建立在异步消息之上的同步指令, 并不一定需要使用 asyncio.Future, 使用多线程应该也不难,进一步思考,似乎只要能够 发送消息,之后根据收到的信号(消息),解除阻塞 ,就能实现我们的目标。(实际上,是自己 DIY 了 Future/Promise)

如果你打算自己在Python中实现 Future,可以参考 ROS 1.0 Python 工具箱。

2023-08-02 更新

以下是在线程中实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# import threading
from concurrent.futures import Future, ThreadPoolExecutor

_futures = {}

def in_coming_message(msg):
    '''
    -> message
    msg
        content
        message_id
    in a task
    '''
    inCommingMessageId =  msg['message_id']
    future = _futures.get(inCommingMessageId)
    if future:
        future.set_result(f"{msg['content']}-{msg['message_id']}")

def _send_and_wait(timeout):
    with ThreadPoolExecutor(max_workers=1) as executor:
        message_id = random.randint(1, 100000000)
        future = Future()
        task = executor.submit(lambda f: f.result(timeout=timeout), future)
        _futures[message_id] = future # todo: threadsafe
        # message["message_id"] = message_id
        # send the message
        return task.result() # block and wait the result or TimeoutError

Squeak

Squeak 对并发有很好的支持。(并发的关键是处理多个任务的能力,并行的关键是同时处理多个任务的能力,Squeak 对多核心的利用目前似乎还不好(并行))

Squeak 支持多种并发模型:

我们在此使用Squeak Actors, 因其实现了 Promise 的概念, 这个库实现的Promises与 Promises/A+ 相似。

值得注意的是在Squeak Actors中, 每个Actors都是 Smalltalk Process,而 Squeak 的Process也只是一个对象,可以在环境里随时与之交互!Squeak 在tools里还提供了 Process Browser

我们暂不使用 Actors 编程模型,只使用它携带的原始 Promise

先展示Promise的简单用法:

1
2
3
p := Promise new.
[Transcript show: (p wait)] fork
p resolveWith: 1

接下来,构建一个类来实现我们的想法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Object subclass: #SyncCmdPromise
              instanceVariableNames: 'promise_resolves'
              classVariableNames: ''
              poolDictionaries: ''
              category: 'mylab'.

"以下是方法"

initialize
	promise_resolves := Dictionary new. 

promise_resolves
	^promise_resolves

pub_cmd: cmd
	"to pub message: cmd"
	| uuid p |
	"uuid := UUID new asString."
	uuid := cmd.
	p := Promise new.
	promise_resolves at:uuid put:p.
	^p.

写一个方法,模拟接收消息,并 resolve promise

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
mock_sub: message_id_list
	"使用线程处理外部消息,目前做法草率,存在线程安全问题,线程间不要共享状态,只做简单通信,用于控制时序(采纳ZeroMQ的观点)"
	
	[message_id_list do: [:message_id | (Delay forSeconds: 2) wait.  (promise_resolves includesKey: message_id) ifTrue: [
		(promise_resolves at:message_id) resolveWith: 'reply ', message_id
		]. 
	].] fork.
	
	Transcript show: (self pub_cmd: 'cmd1') wait.Beeper beep.
	Transcript show: (self pub_cmd: 'cmd2') wait.Beeper beep.
	Transcript show: (self pub_cmd: 'cmd3') wait.Beeper beep.

完整测试它:

1
2
s := SyncCmdPromise new.
s mock_sub: #('cmd1' 'cmd2' 'cmd3')

ps: 如果做实验时,UI 不小心被 wait 阻塞住了,可以按下 Interrupt Key 打破 UI 阻塞。

在不同系统上的规则为:

Macintosh Cmd-.
Linux Alt-.
Windows Alt-.

Snap!

2023-08-02 更新

基本思路:

Demo

以上思路,也可以在 Scratch 里实现: 既可以在 Scratch 表层(积木层)实现(使用 CodeLab Scratch 中的 MQTT 或者 EIM 扩展都能实现),也可以在 Scratch 的下层(JavaScript)里实现。

总结

在不同语言中实现上述想法的时候,我观察到 消息(异步) + Promise/Future 是一种非常强大的模式,灵活且简单。

消息(尤其是pub/sub模式)对并发提供了很好的支持(时序无关),而 Promise/Future 很适合描述时序相关的任务,它们彼此没有耦合,可以随意组合使用。

这块我准备长期探索, 而 Squeak 的极端晚绑定是探索这些新想法的理想之地

The Big Idea is Messaging. – Alan Kay

参考