ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

并发入门实战:通过 Exercism Elixir 的 Take-a-Number 系列理解进程、PID 与 GenServer

并发入门实战:通过 Exercism Elixir 的 Take-a-Number 系列理解进程、PID 与 GenServer 并发入门实战通过 Exercism Elixir 的 Take-a-Number 系列理解进程、PID 与 GenServer【免费下载链接】elixirExercism exercises in Elixir.项目地址: https://gitcode.com/gh_mirrors/elixi/elixir摘要想在实战中理解 Elixir 并发编程的核心概念Exercism 的 Take-a-Number 系列练习正是为此而生。本文将通过「排队叫号机」这一贴近生活的场景带你一步步掌握Elixir 进程的创建、PID的本质、进程间消息传递以及如何用 OTP 的GenServer优雅地管理并发状态。无论你是 Elixir 新手还是想补全并发知识体系的学习者这份实战指南都能帮你建立从裸进程到 GenServer 的完整认知链路。为什么用「排队叫号机」来学习 Elixir 并发银行、医院里的叫号机本质上是一个有状态的服务进程它记住当前发到几号每次叫号递增还能支持重置。这个模型与 Elixir 的进程模型几乎一一对应叫号机 一个 Elixir 进程顾客手里的号码 进程之间的消息当前号码 进程内部维护的状态Exercism Elixir 官方仓库中exercises/concept/take-a-number/与exercises/concept/take-a-number-deluxe/两个练习构成了完美的进阶路线前者让你手写全部并发原语后者让你用OTP 的 GenServer重写同样的逻辑。同样的业务两种实现对比学习的效果远胜死记硬背。对比维度take-a-numbertake-a-number-deluxe进程创建spawn/1手动创建GenServer.start_link/3消息处理手写receive循环handle_call/handle_cast/handle_info状态管理递归参数传递GenServer 内置状态难点等级⭐ 入门⭐⭐ 进阶第一步用 spawn 创建 Elixir 进程并理解 PID在exercises/concept/take-a-number/lib/take_a_number.ex中你的第一个任务测试文件take_a_number_test.exs的 task 1是实现start/0返回一个新的进程标识符PID。Elixir 中所有代码都运行在进程里spawn/1可以显式地在全新进程中执行函数pid spawn(fn - :ok end) # #PID0.125.0这里的关键认知有三点PID 就是进程的邮箱地址——只要拿到 PID就能向该进程投递消息对应概念文档concepts/pids/introduction.mdElixir 进程极其轻量——它不是操作系统进程内存和 CPU 开销都极小一个应用跑成百上千个进程毫无压力每个进程都有独立的邮箱——消息先进入邮箱排队只有进程主动receive才会被取出见concepts/processes/introduction.md。测试会校验is_pid(pid)、pid ! self()确保你返回的是其他新进程的 PID而不是当前进程。第二步Elixir 进程间通信的核心——send 与 receive 消息传递掌握了 PID 之后第二步就是用send/2和receive/1让进程真正说话。take-a-number 的 task 2 要求实现收到{:report_state, sender_pid}消息后把当前号码发回给发送者。send(pid, {:report_state, self()}) # 发消息报告你的状态 receive do number - number # 收到回复 end而进程内部则用receive/1模式匹配邮箱里的消息receive do {:report_state, sender_pid} - send(sender_pid, current_number) :stop - exit(:normal) end理解这条消息传递链路就理解了 Elixir 并发的全部秘密——进程之间不共享内存只交换消息。想收到回复就必须把发送者的 PIDself()放进消息里这是 Elixir 消息传递最经典的习惯用法。第三步用 receive 循环维护进程状态光能收发一次消息还不够叫号机必须持续服务。task 3 要求连续发出 1、2、3、4、5……这需要进程在receive之后递归调用自己把新的号码作为参数传入下一轮——这就是经典的receive loop模式def loop(current_number) do receive do {:report_state, sender_pid} - send(sender_pid, current_number) loop(current_number) # 状态不变继续循环 {:take_a_number, sender_pid} - send(sender_pid, current_number 1) loop(current_number 1) # 状态递增继续循环 :stop - :ok # 退出循环进程结束 end end状态不放在变量里原地修改而是作为递归参数不断传递——这是函数式并发的精髓。task 4 和 task 5 还会要求你处理:stop消息优雅退出并忽略未知消息比如有人对叫号机喊我要找经理 保证进程不被意外消息搞崩溃。第四步从裸进程升级到 GenServer——OTP 行为实战手写 receive loop 能帮你理解原理但生产级 Elixir 项目几乎都用GenServergeneric server通用服务器来封装这套模式。它本质上就是把你手写的 receive loop 抽象成了可复用的行为behaviour概念细节见concepts/genserver/introduction.md。在exercises/concept/take-a-number-deluxe/lib/take_a_number_deluxe.ex中练习给出了完整的客户端 API 骨架start_link/1—— 启动进程对应init/1回调report_state/1—— 查询状态同步handle_callqueue_new_number/1—— 取新号同步handle_callserve_next_queued_number/2—— 叫号支持优先号同步handle_callreset_state/1—— 重置叫号机异步handle_cast你需要实现的正是三个核心回调回调对应客户端函数语义典型返回值init/1start_link/1初始化状态{:ok, state}handle_call/3call/2系函数同步请求需要回复{:reply, reply, state}handle_cast/2cast/2系函数异步请求无需回复{:noreply, state}handle_info/2直接send/2处理任意消息{:noreply, state}状态则用结构体封装在exercises/concept/take-a-number-deluxe/lib/take_a_number_deluxe/state.ex中min_number、max_number、queue号码队列实现见同目录下的queue.ex、auto_shutdown_timeout自动关闭超时。deluxe 版还藏着一个进阶彩蛋——超时自动关闭init/1返回{:ok, state, timeout}后若指定毫秒数内没有消息到达进程会自动触发handle_info(:timeout, state)实现自我回收。这是 GenServer 回调返回三元组的一个绝佳实战案例。实践指南3 步跑通 Take-a-Number 系列练习获取练习源码git clone https://gitcode.com/gh_mirrors/elixi/elixir进入exercises/concept/take-a-number/目录阅读测试驱动开发先看test/take_a_number_test.exs每个测试都标注了task_id按 1→5 的顺序逐个实现lib/take_a_number.ex中的函数运行测试验证在练习目录执行mix test绿灯全亮后再挑战 deluxe 版本take-a-number-deluxe/体会 GenServer 如何把同样逻辑写得更加清晰。总结一条通往 Elixir 并发的完整学习路径通过 Take-a-Number 系列你实际上完成了一次从底层到框架的完整进阶✅ 用spawn创建Elixir 进程理解PID作为邮箱地址的本质✅ 用send/receive完成进程间消息传递✅ 用receive loop 递归维护进程状态✅ 用GenServer封装状态与消息处理掌握 OTP 行为三件套handle_call/handle_cast/handle_info当你能用两种方式实现同一台叫号机Elixir 的并发模型就已经不再是抽象概念而是你信手拈来的实战技能了。下一步不妨把 take-a-number-deluxe 的队列换成Task或Agent再实现一遍——并发世界的大门正为你敞开。【免费下载链接】elixirExercism exercises in Elixir.项目地址: https://gitcode.com/gh_mirrors/elixi/elixir创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表