ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Pony 异步共享计数器:用 Actor 数据所有者模式安全共享可变状态

Pony 异步共享计数器:用 Actor 数据所有者模式安全共享可变状态 编程语言编译器语言运行时【免费下载链接】ponycPony is an open-source, actor-model, capabilities-secure, high performance programming language项目地址https://gitcode.com/gh_mirrors/po/ponyc点击查看免费下载导读examples/actors/counter是 Pony 语言官方仓库中的一个入门级 Actor 示例核心演示了一个异步、共享的计数器多个调用方通过行为behavior向Counteractor 发送增量消息最终由该 actor 汇总并回调打印结果。它揭示了一个非常微妙的并发设计思路——在无锁、无共享内存的 Actor 模型中如何通过数据所有者data owner模式安全地共享可变数据。读完本文你将掌握 Pony Actor 程序的最小结构、be行为与异步消息传递的用法、回调模式actor 把结果发回调用方的写法以及如何在本地编译、运行并动手改造这个示例。示例背景为什么共享可变数据需要专门设计Pony 是一门开源的、基于 Actor 模型、具备 capabilities能力安全机制的高性能语言。在 Actor 模型中每个 actor 拥有自己私有的状态其他 actor 无法直接读写它actor 之间只能通过发送异步消息进行通信。这意味着多个线程共享一份可变数据这种传统的并发写法在 Pony 中无法直接实现——可变数据要么被某个 actor 独占要么以只读的val形式分享。counter 示例正是对这个问题的一种回答把可变数据交给一个专门的数据所有者 actor 独占其他 actor 想要修改或读取数据时只能向它发消息由它代为完成。从外部看多个 actor 都共享了同一个计数器从内部看计数器的字段始终只被Counteractor 自己在自己的线程中访问不存在数据竞争。examples/README.md对它的定位是Demonstrates actor-based mutable state, asynchronous message passing with behaviors, and the callback pattern where an actor sends results back to the caller.演示基于 actor 的可变状态、行为异步消息传递、以及 actor 将结果发回调用方的回调模式。源码结构两个 actor 一台戏counter 示例由三个文件组成位于仓库的 examples/actors/counter 目录下README.md本示例的使用说明即本文依据的文档main.pony全部逻辑代码包含Counter与Main两个 actorcounter.pony仅包含一段文档注释说明程序递增一个基于 actor 的计数器并打印结果。完整的 main.pony 如下use collections actor Counter An actor that counts increments and reports the total. var _count: U32 new create() _count 0 be increment() _count _count 1 be get_and_reset(main: Main) main.display(_count) _count 0 actor Main var _env: Env new create(env: Env) _env env var count: U32 try env.args(1)?.u32()? else 10 end var counter Counter for i in RangeU32 do counter.increment() end counter.get_and_reset(this) be display(result: U32) _env.out.print(result.string())Counter数据所有者Counter是计数器数据的唯一所有者字段var _count: U32是它私有的可变状态只能被Counter自己的方法修改外部代码无法触及构造函数new create()把计数初始化为0行为be increment()每次把计数加一行为be get_and_reset(main: Main)把当前计数通过main.display(_count)回传给调用方随后把计数清零。注意get_and_reset的参数类型是Main——即调用方 actor 本身。这就是回调模式的载体Counter完成读取后不直接打印而是把结果作为参数通过异步消息发回Main由Main的display行为负责输出。这样两个 actor 之间始终只交换消息不共享任何内存。Main入口与回调接收方Main是每个 Pony 程序必需的入口 actornew create(env: Env)是程序启动时执行的构造函数env提供命令行参数、标准输出等运行时环境构造函数内部解析参数、创建Counter、发送增量消息最后调用get_and_reset请求结果行为be display(result: U32)把结果打印到标准输出。Env的定义位于 packages/builtin/env.ponylet args: Array[String] val保存命令行参数let out: OutStream对应标准输出。主流程中用到的_env.out.print(result.string())即通过out把数字转成字符串后打印。如何编译按原文档说明安装一个最小的 Pony 环境即包含ponyc编译器后在 examples/actors/counter 目录下直接运行ponycponyc会自动发现当前目录下的.pony文件、构建所需的包编译过程以如下输出结束... Generating Reachability Selector painting Data prototypes Data types Function prototypes Functions Descriptors Optimising Writing ./counter.o Linking ./counter结束后当前目录会生成可执行文件./counter以及中间产物counter.o。说明这里不需要指定入口文件因为该目录下只有main.pony与counter.pony且Mainactor 是标准入口编译命令在 README 同级目录执行即可。仓库中 examples/actors/ring/README.md 等同类示例也采用完全相同的编译方式可作为对照。如何运行编译成功后在相同目录下执行$ ./counter 100 100程序读取第一个命令行参数100向Counter发送 100 次increment()然后请求结果并打印出累计值100。若不带参数运行./counter默认值会是10见下文参数解析。命令行参数解析细节从源码可见参数解析集中在Main.create这一行var count: U32 try env.args(1)?.u32()? else 10 end它做了三件事env.args(1)取出第一个命令行参数下标 1下标 0 是程序自身路径返回String??.u32()把字符串解析为无符号 32 位整数解析失败会抛出error外层try ... else 10 end捕获任何错误包括缺参数、参数非法、数字溢出回退到默认值10。因此./counter 100输出100而./counter则输出10传入非数字如./counter abc同样会回退到 10。增量循环与Range发送 100 次增量用的是for循环配合Rangefor i in RangeU32 do counter.increment() endRange来自标准库的 packages/collections/range.pony它产生min, max)左闭右开区间步长默认为 1。这里Range[U32会依次产出0, 1, ..., count-1恰好驱动count次increment()调用。Range是Iterator[A]的实现for循环会隐式调用其has_next()与next()见该文件第 113–128 行所以循环次数精确等于count。消息的异步性与执行时机一个需要理解的关键点是for循环里连续调用counter.increment()只是把消息投递进Counter的 mailbox并不会同步执行递增。Counter会在自己的调度时机逐条处理这些消息。紧接着的counter.get_and_reset(this)同样只是投递一条请求消息它排在上面的增量消息之后由Counter按序处理——因此最终Main.display收到的必然是全部增量完成后的计数值。这正是 Actor 模型消息有序、状态私有的体现虽然调用方无法预知消息何时被处理但同一调用方发给同一 actor 的消息保持发送顺序。程序修改证明计数是实时发生的原文档指出如果想让程序证明计数器是实时运行而不是提前算好结果可以增加一个行为来观察执行过程。在原程序基础上做如下修改actor Counter ... be is_running(main: Main) main.still_running() ... actor Main ... for i in RangeU32 do counter.increment() counter.is_running(this) end ... be still_running() _env.out.print(Your message here)改动包含三部分给Counter新增行为is_running(main: Main)它不修改任何状态只向Main回发一条我还活着的通知在Main的循环里每次increment()之后紧接着调用counter.is_running(this)给Main新增行为still_running()打印一条消息。运行后每条increment()之后都会打印一行 Your message here。由于消息是异步投递的打印行数与增量次数一致但打印本身穿插在Counter的处理过程中直观地说明计数确实是在 actor 之间逐条消息传递中实时累加的而不是在启动阶段一次性预计算。动手练习把当前计数回显到每次递增之后原文档在结尾留下了一个练习建议读者修改程序让每次counter.increment()之后都能display(...)出当前计数值。文档给出的提示是需要给increment()行为增加一个参数。参考现有回调模式一种实现思路是把increment()改成带回调参数的形式例如be increment(main: Main) _count _count 1 main.display(_count)相应地Main的循环变为for i in RangeU32 do counter.increment(this) end此时无需再单独调用get_and_reset因为每次递增后Counter都会把最新计数发回Main.display打印。运行./counter 5会依次输出1、2、3、4、5而不是最终的总数。这个练习的价值在于让你亲手体验两条 Pony 并发惯例行为参数即回调actor 之间的请求-响应靠把调用方自身this作为参数传进行为来实现无需返回值消息有序性increment(this)与display构成一个往返打印的顺序与递增的顺序严格一致这正是依赖同一 actor mailbox 的按序处理保证。从示例到模式数据所有者与回调counter 虽小却浓缩了 Pony Actor 编程的三大核心模式在仓库其他示例中反复出现数据所有者data owner模式可变状态被单一 actor 独占外部通过消息读写。这是原文档点名的核心思想——safely share mutable data by dedicating a data owner, then communicating with that owner通过指定一个数据所有者并与之通信来安全地共享可变数据。类似做法也体现在 examples/actors/spreader 的树形结果聚合、examples/actors/ring 的环形消息传递等示例中。回调模式处理完请求的 actor 把结果作为参数发回调用方。本示例中get_and_reset(main: Main) main.display(_count)是最精简的回调写法examples/README.md在描述 counter 时也明确指出了这一点。行为be即异步入口be声明的方法没有返回值调用即投递消息执行由目标 actor 的调度决定。这种设计让函数调用在跨 actor 时天然无阻塞、无锁。小结examples/actors/counter是理解 Pony 并发模型的最佳起点之一用不到 40 行代码讲清了actor 独占可变状态 行为异步消息 回调回传结果这一整套协作方式。按照本文的编译、运行与修改步骤你可以在本地快速验证ponyc编译、./counter 100输出100、通过新增is_running/still_running观察实时执行、再通过给increment()加参数完成回显练习。掌握了这个最小闭环再去阅读 examples/actors 下 mailbox、ring、spreader 等更复杂的 Actor 示例就会顺畅得多。赞分享编程语言编译器语言运行时【免费下载链接】ponycPony is an open-source, actor-model, capabilities-secure, high performance programming language项目地址https://gitcode.com/gh_mirrors/po/ponyc点击查看免费下载相关推荐Electric Agents Blackboard共享状态模式用持久化共享数据协调多 Agent 协作Electric Agents Blackboard共享状态模式用持久化共享数据协调多 Agent 协作 Blackboard黑板/共享状态是 EleNW.js多窗口应用设计模式状态同步与数据共享终极指南NW.js多窗口应用设计模式状态同步与数据共享终极指南 NW.js作为一款能够直接从DOM/WebWorker调用Node.js模块的应用框架为开发者提供了桌面应用跨平台vue-admin-better状态管理模式单向数据流与状态共享vue admin better状态管理模式单向数据流与状态共享 在前端开发中应用状态管理是构建复杂交互界面的核心环节。当多个组件需要共享用户登录状态、全局前端认证鉴权管理后台上一篇iOS数据安全终极指南使用JKCategories的NSData加密与NSDictionary安全访问下一篇DesignPatternsPHP可测试性模式实现的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进