ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

language-ext 并发编程指南:Atom 无锁状态、AtomHashMap/AtomSeq 与 STM 软件事务内存

language-ext 并发编程指南:Atom 无锁状态、AtomHashMap/AtomSeq 与 STM 软件事务内存 后端【免费下载链接】language-extC# pure functional programming framework - come and get declarative!项目地址https://gitcode.com/gh_mirrors/la/language-ext点击查看免费下载函数式编程推崇不可变类型但在真实系统中我们偶尔仍然需要共享的可变状态。language-ext 在LanguageExt.Core/Concurrency模块中提供了一整套无需lock的并发原语用Atom保护任意值、用AtomHashMap/AtomSeq把不可变集合包装成无锁可变结构且快照免费以及基于多版本并发控制MVCC的软件事务内存STM系统。读完本文你将掌握这三种工具的使用场景、核心 API、底层实现原理并能在自己的 C# 项目中直接替换掉传统的加锁代码。为什么需要无锁的共享状态在纯函数式编程中我们倾向于使用不可变类型但并非所有场景都能做到完全不可变有时候我们确实需要一些共享的可变状态。如果直接使用语言库中的不可变集合你就需要用锁来保护每次更新// 一些全局状态 HashSetint set HashSet(1, 2, 3); object sync new(); lock(sync) { set set.Add(4); }这种写法存在诸多问题锁的获取与释放容易出错、可能引入死锁、并带来上下文切换的开销。为此LanguageExt.Core/Concurrency/README.md 专门定义了一个并发模块其核心思路是用无锁的原子操作替代锁。上面的代码用AtomHashSet可以简写为AtomHashSetint set AtomHashSet(1, 2, 3); set.Add(4);无锁并发的完整工具集位于 LanguageExt.Core/Concurrency 目录包含Atom—— 保护任意值的无锁容器Atom.csAtomHashMap / AtomSeq / AtomQue—— 对HashMap、Seq、Queue的无锁可变包装STM 系统—— 允许对多个Ref值进行事务性修改STM.cs、Ref.csSignals、VectorClock、VersionVector、VersionHashMap等并发辅助设施。模块的对外入口是 Prelude.Concurrency.cs它把Atom、Ref、swap、commute、atomic、snapshot、serial等操作以Prelude函数的形式暴露出来。Atom无锁保护任意值Atom提供了一种无需锁即可管理共享、同步、独立状态的方式。你可以把不可变数据结构放进去然后用各种Swap方法原子地更新它或者通过Value属性读取它详见 Atom/README.md。基本用法record Person(string Name, string Surname); // 创建一个新 atom var person Atom(new Person(Paul, Louth)); // 原子地修改它 person.Swap(p p with { Surname ${p.Name}y }); // 获取 atom 状态的一个快照 var snapshot person.Value;Prelude.Atom构造函数的底层实现见 Prelude.Concurrency.cs提供了多种重载重载说明AtomA(A value)创建无验证器的 Atom任何新状态都会被接受AtomA(A value, FuncA, bool validator)创建带验证器的 Atom返回OptionAtomA若初始值不通过验证则返回NoneAtomM, A(M metadata, A value)带元数据的 Atom元数据会传给验证函数AtomM, A(M metadata, A value, FuncA, bool validator)同时带元数据与验证器的 Atom在 Atom.cs 的New内部构造中验证器会在 Atom 创建时先对初始值执行一次校验从而保证Atom 永远不会处于非法状态此后每次状态变更也都会经过同一个验证器。Swap 的底层机制CAS 自旋重试Atom的核心是Swap方法其实现Atom.cs遵循典型的CASCompare-and-Swap自旋循环读取当前值current将当前值传给函数f计算新值newValue如果验证器拒绝新值直接返回当前值不更新通过Interlocked.CompareExchange尝试把新值原子地替换进去如果 CAS 失败说明有其他线程抢先修改了状态调用SpinWait.SpinOnce()短暂自旋后重试直到成功。由此可以得出两个重要的使用约束README 与源码注释都反复强调f可能被调用多次因为并发竞争时会被重试所以f必须是无副作用的纯函数否则一切皆有可能应尽量缩短f的执行时间以降低与其他线程竞争的窗口减少重试次数。此外Atom还提供SwapMaybef返回OptionA返回None时不更新、SwapIO/SwapMaybeIO把 swap 提升为IO单子、以及ValueIO可重复求值以获取最新状态的IO读取器。每个成功更新都会触发Change事件类型为AtomChangedEventA便于做变更通知。AtomHashMap 与 AtomSeq无锁的集合包装单个Atom每次只能原子更新一个值。当你想对HashMap这样的集合做读取-修改-写入的复合操作时AtomHashMap把整个HashMap包装进一个无锁结构AtomHashMap.cs内部持有的是一个volatile TrieMap。构造与快照// 空集合 var map AtomHashMapstring, int.Empty; // 从普通 HashMap 转换见 AtomHashMap.Module.cs HashMapstring, int hm HashMapstring, int(((a, 1), (b, 2))); var amap hm.ToAtom(); // 构造后即可并发使用关键是快照是免费的由于底层是不可变HashMap结构共享读取Value/Items得到的就是当前状态的不可变快照可以安全地随意读取而无需加锁。Swap整个集合的原子更新AtomHashMap.Swap接受一个FuncTrackingHashMapK, V, TrackingHashMapK, V把当前整个 map映射为新的 map并原子地一次性提交AtomHashMap.csamap.Swap(m m.Add(c, 3).Remove(a));这段代码把添加和删除作为一次原子操作完成中间状态对外不可见。与Atom.Swap同理swap 函数可能被多次执行因此必须无副作用、尽量短小若ReferenceEquals判断新旧 map 相同无变化则不触发变更事件直接返回只有通过Interlocked.CompareExchange成功提交后才会调用AnnounceChanges广播变更。SwapKey则用于只针对某个 key 的值做原子更新若 key 不存在则什么都不做AtomHashMap.cs。AtomHashMap实现了IEnumerable(K Key, V Value)、IReadOnlyDictionaryK, V等接口因此可以像普通只读字典一样遍历与索引访问。同类工具还有AtomSeq包装Seq、AtomQue包装Queue。STM软件事务内存与 Ref当需要同时、原子地修改多个状态时单个Atom就不够用了。language-ext 的软件事务内存STM系统支持对多个Ref值进行事务性修改见 Concurrency/README.md 与 STM.cs。Ref被事务追踪的存储位置RefA只是包装了对一个值的访问并让该值的状态变化可以被 STM 追踪Ref.cs。每个Ref在创建时被分配唯一 Id其Value的读取STM.Read与写入STM.Write只能在事务内进行var x Ref(1); var y Ref(2);// 在同一事务内同时修改 x 和 y atomic(() { x.Value x.Value 1; y.Value y.Value 1; });Ref支持Swap在事务内把旧值替换为f的返回值、Commute见下文以及ValueIO。与Atom相同Ref的值也必须是或被视为不可变的——STM 事务中的所有工作都是推测性的如果值可变事务的回滚就无从谈起。事务的 ACID 语义sync事务即atomic/snapshot/serial包装的代码块类比数据库事务对Ref的修改保证Atomic原子性事务中对所有Ref的修改要么全部发生要么全部不发生Consistent一致性每个新值在提交前都要经过验证器validator校验Isolated隔离性事务运行期间看不到其他事务的任何影响。此外冲突的事务会被自动重试。language-ext 的 STM 使用**多版本并发控制MVCC**实现快照与可串行化隔离体现在实践中就是事务内对Ref的所有读取都会看到事务起点read point的一致快照同时能看到自身做出的修改即in-transaction-value事务内对Ref的所有修改看起来是在写点这一瞬间同时发生的读者永不阻塞写者写者也永不阻塞读者。隔离级别Snapshot 与 SerialisableIsolation.cs 定义了两种隔离级别Snapshot快照隔离默认只要求事务外部没有修改过事务内被写入的Ref。如果写冲突事务回滚并按最新状态重试Serialisable可串行化要求事务外部没有修改过事务内被读取或写入的所有Ref。这是最严格的隔离级别最容易冲突但能防止跨读写的读-写不一致。看一个官方文档给出的典型对比见 Prelude.Concurrency.csvar x Ref(1); var y Ref(2); // 快照隔离只写 x、只读 y // 即使事务中途有人写 y也不会导致事务失败 snapshot(() x.Value y.Value 1); // 可串行化隔离读写了 x 和 y // 如果有人写 y事务就会失败并重试 serial(() x.Value y.Value 1);事务入口atomic / snapshot / serialPrelude提供三个事务入口均转发到STM.DoTransactionPrelude.Concurrency.cs函数隔离级别说明atomic(op)/atomic(op, Isolation)可指定默认 Snapshot通用事务入口snapshot(op)Snapshot快照隔离事务serial(op)Serialisable可串行化事务每个入口都有FuncR与Action两种重载返回R或Unit还支持ValueTask与Eff异步/效果版本。关键特性是如果已经处于一个事务中嵌套调用会成为父事务的一部分而不是另起新事务见 STM.cs 的DoTransaction实现。事务的提交与冲突重试STM.RunTransactionSTM.cs的执行流程如下用当前世界状态创建一个新Transaction并把它放进AsyncLocal执行事务操作op调用ValidateAndCommit校验并提交若隔离级别为Serialisable先执行ValidateReads逐个比对读取的Ref版本号是否变化变化则抛ConflictException对每个写入先运行验证器不通过抛RefValidationFailedException再检查版本号版本一致则Inc()版本后写入否则抛ConflictException捕获ConflictException后清空当前事务、广播变更短暂自旋后重新执行整个事务最终提交由底层AtomHashMap的原子 swap 完成保证多个Ref的修改一次性生效。Commute更高并发度的提交对于可交换的更新操作可以使用commutePrelude.Concurrency.cscommute(x, v v 1);Commute允许在提交时再次以最新已提交值执行f因此事务之间可以重叠而不必互相阻塞比直接设置Ref值允许更高的并发。注意f必须是可交换的commutative否则就要接受最后写入者胜出last-one-in-wins的行为CommuteRef类型与atomic的重载STM.cs专门处理这类提交。在Prelude.Concurrency.cs中swap(r, f)对应Ref.Swap必须运行在事务内commute(r, f)对应Ref.Commute。使用建议与注意事项综合文档与源码以下几点是使用该并发模块的硬性约束Swap/事务函数必须无副作用且尽量短小。无论是Atom.Swap、AtomHashMap.Swap还是 STM 事务都可能在冲突时被多次执行f的耗时直接决定了竞争窗口的大小。放入Atom/Ref的值必须不可变。否则 CAS 重试与事务回滚都无从谈起language-ext 的持久化集合HashMap、Seq、Lst、Set等天然适合作为这些容器的值因为它们的拷贝是免费的、修改是结构共享的。I/O 和带副作用的操作要远离事务因为事务随时可能回滚重试。选择正确的隔离级别单值原子更新用Atom单集合复合操作用AtomHashMap.Swap多值事务更新用snapshot默认或serial需要防读写不一致时。验证与测试并发模块的正确性在仓库测试中有充分覆盖可以按图索骥深入验证AtomHashMapTests.cs 与 AtomHashMapEqTests.cs 覆盖了无锁哈希表的构造、更新与相等性AtomTests.cs 覆盖Atom的创建、Swap与快照语义VersionHashMapTests.cs 与 VectorClockTests.cs 验证分布式并发辅助结构Samples/TestBed/AtomHashMapTests.cs 提供了可运行的实测样例。小结language-ext 的Concurrency模块为 C# 开发者提供了三条递进的并发工具链单值无锁原子更新用Atom集合级无锁复合操作用AtomHashMap/AtomSeq多值事务性修改用基于 MVCC 的 STMRefatomic/snapshot/serial。三者共享同一设计哲学——以不可变数据 CAS 自旋重试替代加锁在保证线程安全的同时消除死锁风险并让快照读取变得零成本。在需要共享可变状态的场景中这套无锁工具集是比lock更符合函数式风格的选择。赞分享后端【免费下载链接】language-extC# pure functional programming framework - come and get declarative!项目地址https://gitcode.com/gh_mirrors/la/language-ext点击查看免费下载相关推荐Arrow STM 并发编程构建无锁并发系统的完整教程Arrow STM 并发编程构建无锁并发系统的完整教程 在现代软件开发中 并发编程 已成为必备技能但传统的锁机制往往带来死锁、竞态条件等复杂问题。今天我后端Concurrent Ruby TVar和STM软件事务内存的Ruby实现与实战应用Concurrent Ruby TVar和STM软件事务内存的Ruby实现与实战应用 在现代并发编程中 软件事务内存Software Transactio开发工具深入理解软件事务内存(STM)及其在JVM中的应用深入理解软件事务内存 STM 及其在JVM中的应用 引言并发编程的痛点与STM的诞生 在传统的多线程编程中开发者常常陷入同步共享可变内存的困境。使用 syn文档教程知识库上一篇OpenNHP 零信任网络隐藏协议架构详解NHP-Agent / NHP-Server / NHP-AC 核心组件与访问工作流下一篇明日方舟桌宠 Ark-Pets 快速上手指南让干员住进你的桌面五分钟完成安装与配置创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表