You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 19:32:44