.NET新版本中Volatile.Read读取int ref失效,改用Value是否线程安全?
关于F#中QueuedLock实现的线程安全疑问
问题背景
我将C#的QueuedLock代码移植到F#后,该代码多年运行正常,但在.NET新版本的CI构建中出现编译错误:Volatile.Read ticketToRide无匹配重载(参数类型为int ref)。改用ticketToRide.Value可通过编译,但不确定该方式是否线程安全,特此咨询。
原C#实现
using System.Threading; public sealed class QueuedLock { private object innerLock; private volatile int ticketsCount = 0; private volatile int ticketToRide = 1; public QueuedLock() { innerLock = new Object(); } public void Enter() { int myTicket = Interlocked.Increment(ref ticketsCount); Monitor.Enter(innerLock); while (true) { if (myTicket == ticketToRide) { return; } else { Monitor.Wait(innerLock); } } } public void Exit() { Interlocked.Increment(ref ticketToRide); Monitor.PulseAll(innerLock); Monitor.Exit(innerLock); } }
原F#实现(编译报错版本)
type internal QueuedLock() = let innerLock = Object() let ticketsCount = ref 0 let ticketToRide = ref 1 member __.Enter () = let myTicket = Interlocked.Increment ticketsCount Monitor.Enter innerLock while myTicket <> Volatile.Read ticketToRide do Monitor.Wait innerLock |> ignore member __.Exit () = Interlocked.Increment ticketToRide |> ignore Monitor.PulseAll innerLock Monitor.Exit innerLock
解答
编译错误原因
.NET新版本中Volatile.Read的重载不直接支持F#的引用单元(ref类型)——F#的ref是封装了值的引用单元对象,和C#的ref参数语义不同,因此无法直接传入Volatile.Read。
ticketToRide.Value的线程安全性
改用ticketToRide.Value是线程安全的,原因如下:
- 在
Enter方法中,我们已经通过Monitor.Enter持有了innerLock的锁。根据.NET内存模型,Monitor的Enter和Exit操作会建立完整的内存屏障,确保持有锁期间的读取操作能获取到其他线程修改后的最新值,不存在可见性问题。 - 在
Exit方法中,我们使用Interlocked.Increment修改ticketToRide,Interlocked系列操作本身是原子的,且自带内存屏障,修改后的结果会立即对其他线程可见。
修正后的F#代码
type internal QueuedLock() = let innerLock = obj() let ticketsCount = ref 0 let ticketToRide = ref 1 member __.Enter () = let myTicket = Interlocked.Increment ticketsCount Monitor.Enter innerLock while myTicket <> ticketToRide.Value do Monitor.Wait innerLock |> ignore member __.Exit () = Interlocked.Increment ticketToRide |> ignore Monitor.PulseAll innerLock Monitor.Exit innerLock
额外说明
如果一定要保留Volatile.Read的用法,可以通过获取引用单元内部值的地址来调用:Volatile.Read &ticketToRide.Value,但在当前场景下这是冗余的——因为Monitor锁已经提供了足够的可见性保证,不需要额外的Volatile.Read。
内容的提问来源于stack exchange,提问作者knocte
相关产品推荐
相关产品推荐

