Go 18:共享任务状态怎样避免竞争与终态反转
第 17 篇已经把取消请求和退出确认分开。另一个问题随之出现:worker 正在写任务结果时,查询者或取消者同时读写这个 ID,该读到 RUNNING 还是 SUCCEEDED?如果状态已写成 SUCCEEDED,结果的词数却还是旧值,终态就不再是一条完整事实。单纯“让多个 goroutine 都能访问 map”并不构成服务契约。本章在同一个累计工程增加内存状态提交点,并且保留有锁、无锁同一竞争场景的原始证据。工程与实验完整包、SHA-256可独立下载复跑。 独立复跑步骤列出本章正反例、源码校验及运行命令。 一条任务记录有几个需要一致的字段 从上一阶段沿用的 TaskRecord 有 ID、Status、统计的 Words/Bytes/Runes。本章再加 CancelRequested,它只表示有人提出请求,不能代替 CANCELED 终态。新 TaskStore 把按 ID 查询的记录保存在 map 中,sync.Mutex 放在 store 上,而不是嵌进会被复制的记录值。go doc sync.Mutex 明确说 Mutex 首次使用后不能复制;Lookup 在锁内取出记...
Python 35:关闭服务时哪些任务还能提交结果
回环服务停止接收新连接时,旧连接可能还卡在半行请求上,已经被事件循环接手但尚未进入服务处理函数的迟到 handler 也可能在关闭开始后才运行。直接等 Server.wait_closed() 就算设了排空期限,也可能被旧连接拖住。最初的测试确实失败:它原本要取消一个未完成处理器,却在读超时后才退出,得到 cancelled_handlers=0。失败输出留在 examples/python-language/evidence/35/20261008-shutdown-failed/;修复是先设置关闭门禁并停止接收,再等待已有处理器至指定期限、取消剩余处理器并等它们退出,最后才 wait_closed()。 34 的 JSON 行接口保留每连接一个请求。正常案例中,完整提交请求已回复任务 ID,服务排空没有取消处理器;半行案例设 drain_timeout=0,现有连接收到 EOF、处理器从活动集合移除,新连接不再被接收。补充回归还把 _serve_client 包上一道测试屏障:连接已经进入事件循环,close() 设置 _closing 后才放行 handler。完整提交帧...
Python E03:C 扩展和缓冲区怎样交换数据
tasklab 的词频任务可以附带 CPU 工作负载:把文本的每个字符按固定公式折叠成校验值,重复指定次数。这个值只用于执行模式对照,不会替换词频查询结果。把循环搬进 C 扩展之前,要先回答两个问题:输入 str 如何交给按字节读取的 C 函数,以及 C 持有数据的期间由谁保证它仍然有效? 本次复跑使用仓库外的两个精确解释器:CPython 3.12.3 macOS arm64 与 CPython 3.13.9 experimental free-threading macOS arm64。E03_build.py 直接读取目标解释器的 sysconfig 编译参数,把 .o 和 .so 都放在 /private/tmp/python-language-completion/native/;源码目录只保存 C 源码、构建脚本、比对脚本和证据。扩展不是主线 wheel 的隐藏依赖。 迁移的是哪个循环 纯 Python 基线 cpu_work(text, iterations=...) 对 str 逐字符取 ord,每步执行 (checksum * 33 + value) % 10...
Go 17:取消请求发出后工作是否真的退出
第 16 篇的生产者可能停在满通道的发送处,消费者可能停在空通道的接收处,也可能已经开始统计。此时调用方不再想等,应该做什么?直接返回会留下自己启动的 goroutine;关闭 inputs 会让仍在发送的生产者 panic。context 给了“请停止”的信号,却不负责证明接收者已经收到并执行。这里要分别验证两件事:取消请求能否打断每种等待,以及返回前是否真的收到这些工作的退出确认。 本章继续扩展同一个累计工程,没有另起服务。queue.go 的单生产者、单消费者现在接收 context.Context;原统计链新增逐 rune 检查的可取消版本。CLI 增加 --timeout,从文本/JSON/批次输入准备完以后才开始给任务处理计时。本章完整累计包、SHA-256与解包说明可下载;实验的实际命令、双流及退出码见 writing-plans/go-language/evidence/17/。 取消能解开哪几处阻塞 发送端调用 sendTask(ctx, inputs, task),内部 select 等待输入通道可写或 ctx.Done();接收端 receiveTask(c...
Python E02:泛型协议怎样表达插件边界
tasklab.run 已有一个可选 workload 回调:它接收任务文本,在词频计算前运行,但它的返回值不会成为任务结果。如果给它包一层事件记录器,静态检查器还能看见原处理器的参数和返回值吗?只标成 Callable[..., object] 虽然方便,却会让 label=42 之类错误在调用处溜过去。这个选修保留应用的词频提交路径,只让处理器扩展边界在代码里变得可检查。 主线14 的协议讲运行时对象能否按所需操作使用,18 的类型提示已经把静态断言和动态输入校验分开。这里再加一个问题:装饰处理器之后如何不丢签名? 只包现有钩子,不另造执行器 在 src/tasklab/optional_plugins.py 中,可调用协议按输入参数和返回值参数化: 123456Parameters = ParamSpec("Parameters")ReturnValue = TypeVar("ReturnValue", covariant=True)class Processor(Protocol[Parameters, ReturnValue])...
Python 34:服务接口如何复用既有任务规则
客户端把 {"op":"submit",...} 写入连接并成功 drain(),任务完成了吗?服务可能还没读到换行符,连请求边界都无法确认;即使收到含任务 ID 的提交回复,新任务也仍是 queued。要验证结果,必须再执行 run 并核对返回的业务状态和结果。传输层写入、应用接受和业务完成是三个不同事件。 本篇在 127.0.0.1 的临时端口提供每连接一个请求、每请求一行 UTF-8 JSON 的小接口。submit/run/query/cancel 直接复用已有 app 操作;run 调用 31 的 run_async并在完成后读取任务快照。它没有第二套状态机,也没有允许客户端上传 Python 函数。测试中提交得到 task-0001,立刻查询还是 queued;执行返回 {"one":2,"two":1} 与 succeeded。21 的 SQLite 事务没有被这个内存服务自动接入;重启后的持久化和停机恢复留待 35。 一行也需要边界 服务以 S...
Go 16:channel 的发送、接收与关闭怎样构成协议
上篇让统计函数在 goroutine 中运行,但 CLI 还是启动一项、等一项。要把任务交给另一个持续读取任务的计算者,仅有“某项结束了”的 done 不够:谁能继续发送?谁宣布不会再发?接收者如何知道该退出?关闭错了 channel,程序可能 panic;不关闭,又可能永远等在接收处。这些问题需要一份双方能遵守的协议,不是画一条 chan 箭头就解决。 本章沿用同一个任务工程和旧的任务 ID、错误分类及 CLI 入口。queue.go 增加一个生产者、一个消费者和两条 channel;CLI 在校验及输入准备后把任务交给生产者,最终排空结果,再生成原有记录。没有为本章另起一个互不相关的服务。完整累计快照、SHA-256和解包命令可独立下载复跑;原始命令、退出码和双流另存 writing-plans/go-language/evidence/16/。 首先约定唯一关闭者 任务路径如下: 123456789CLI 的 tasks 切片 │ ▼生产者 ──发送──> inputs(容量 1) ──接收──> 消费者 │ ...
Python 33:有并发上限为何还会占满内存
两个工作者都在等待一扇没打开的事件门。如果只限制工作者数量,生产者仍可以无限次 queue.put(),请求在排队时照样占内存。即使队列有上限,服务入口若给每个请求创建一个等待队列空位的 Task,等待入场的 Task 本身也没有上限。 32 的任务组约束故障和取消后子任务如何退出;本篇先限制可接收但尚未完成的数量。AsyncTaskQueue 配两个工作者和一个排队槽,入口同时检查 admitted < workers + waiting 与 Queue.full(),用 put_nowait() 入队。无空位时同步抛 BusyError,不创建任务 ID,也不在后台制造另一个等待队列。校验失败的输入仍由已有 app.submit 拒绝,不消耗容量。 谁占用容量 队列里的 queued 工作与工作者里 running 的工作都占一个 admitted 配额。测试用 Event 把两个工作者停在 run_async 的等待点,再递交第三项:快照为 running=2, queued=1, admitted=3, capacity=3。第四次提交立即被拒绝,store 中实...
Python E01:描述符和元类何时值得使用
给任务状态赋值,看起来可以把原先的 @property 换成“更通用”的描述符。当前对照运行把普通 Task 与候选 Candidate 放在同一组输入下:"unknown" 与 None 均抛 ValueError: unknown task state,[] 在两边均抛 TypeError: unhashable type: 'list'。原因不在描述符:主线 Task.state 也用 value not in TASK_STATES,集合成员判断要求输入可哈希。这里修正的是对原合同的描述,不是悄悄改变应用行为。 本文只回答一个选型问题:tasklab 现在是否需要把状态验证提升到描述符或元类?先运行相同输入,再看协议与维护成本。它不把抽象机制当作必须加入主线的新功能。12 的绑定方法和22 的状态断言是这里的前置。 属性访问经过谁 Task.state 已经是一个数据描述符:property 定义了读写逻辑,task.state = value 会调用 setter,不是简单地向实例字典塞一个同名键。现有 setter 在 RLock 下校验状态并更...
Python 32:一个子任务失败后其他任务怎么办
三个任务已经进入 running:一个准备计算时抛异常,另外两个还在等永远不会设置的 Event。若只等待失败任务,两个等待中的兄弟任务可能留在后台。业务库里看到 failed,并不能据此推断同批其他任务都停止了。 31 的 asyncio 入口保留任务状态合同。本篇增加 run_batch:每个 Task 都在 asyncio.TaskGroup 中创建;正常退出组时收集词频结果,某个任务首次抛出非 CancelledError 异常时,任务组取消其余成员、等待它们退出,再向调用方抛出异常组。批次不允许同一任务 ID 重复提交;在安排任何工作前先拒绝重复项。 失败以后先等谁退出 测试给三项工作各设一扇事件门,等三边都报告 running 才只放行故障任务。该任务触发可控 RuntimeError,经业务入口转为 TaskExecutionError 并记录 failed;另外两扇门始终没有打开。任务组取消还在等门的两个兄弟任务;run_async 接收 CancelledError 时标记 cancelled 并重新抛出。外层 worker 的 finally 在每个子任务结...










