vb.net多线程环境中绝对不能直接读取全局变量值,必须通过安全方式获取、更新最新值

在多线程环境中,即使是 _serverInfoState 这样的整数变量,直接读取也可能存在可见性问题(不同线程可能看到缓存中的旧值),因此需要通过原子操作确保读取到最新值。

(比喻:关键词:多线程中绝对不能直接操作变量,相当于要包装一层,库存数量变动,银行存款减了多少,全不能直接读写数据库,要包装一个存储过程,还要加事务)

以下是完善后的 VB.NET 模块代码,包含加 1、减 1、加 N、减 N 等原子操作,并规范了安全的读写方式:

多线程安全的VB.NET模块(含原子操作)

Imports System.Threading

Module ServerStateModule
    ' 模块级变量(默认共享,所有线程可见)
    ' 注意:公开变量可能导致直接修改,建议私有并通过方法操作
    Private _serverInfoState As Integer = 0 ' 0=关闭,1=开启(可扩展为计数)

' 安全判断是否开启(合并版)
Public Function IsServerInfoEnabled() As Boolean
    ' 直接通过原子操作读取最新值并判断
    Return Interlocked.CompareExchange(_serverInfoState, 0, 0) = 1
End Function

    ' 安全读取当前值(保证可见性)
    Public Function GetCurrentState() As Integer
        ' 用CompareExchange读取最新值(不修改原变量)
        Return Interlocked.CompareExchange(_serverInfoState, 0, 0)
    End Function

    ' 安全判断是否开启
    Public Function IsServerInfoEnabled2() As Boolean
        Return GetCurrentState() = 1
    End Function

    ' 原子设置为开启(1)
    Public Sub EnableServerInfo()
        Interlocked.Exchange(_serverInfoState, 1)
    End Sub

    ' 原子设置为关闭(0)
    Public Sub DisableServerInfo()
        Interlocked.Exchange(_serverInfoState, 0)
    End Sub

    ' 原子切换状态(0↔1)
    Public Sub ToggleServerInfo()
        Do
            Dim current = _serverInfoState
            Dim newState = If(current = 1, 0, 1)
        Loop While Interlocked.CompareExchange(_serverInfoState, newState, current) <> current
    End Sub

    ' 原子加1
    Public Function Increment() As Integer
        ' 返回递增后的值
        Return Interlocked.Increment(_serverInfoState)
    End Function

    ' 原子减1
    Public Function Decrement() As Integer
        ' 返回递减后的值
        Return Interlocked.Decrement(_serverInfoState)
    End Function

    ' 原子加N(支持正数)
    Public Function AddN(value As Integer) As Integer
        If value <= 0 Then
            Throw New ArgumentException("增加值必须为正数", NameOf(value))
        End If
        ' 循环累加,确保原子性(Interlocked无直接Add方法,需模拟)
        Dim initial, updated As Integer
        Do
            initial = _serverInfoState
            updated = initial + value
        Loop While Interlocked.CompareExchange(_serverInfoState, updated, initial) <> initial
        Return updated
    End Function

    ' 原子减N(支持正数,确保结果不小于0)
    Public Function SubtractN(value As Integer) As Integer
        If value <= 0 Then
            Throw New ArgumentException("减少值必须为正数", NameOf(value))
        End If
        ' 循环递减,确保原子性并防止负数
        Dim initial, updated As Integer
        Do
            initial = _serverInfoState
            updated = Math.Max(initial - value, 0) ' 确保不小于0
        Loop While Interlocked.CompareExchange(_serverInfoState, updated, initial) <> initial
        Return updated
    End Function


End Module

关键说明:

  1. 为什么不能直接读取 _serverInfoState

    • 在多 CPU 环境中,线程可能从各自的缓存中读取值,而非内存中的最新值(缓存一致性问题)。
    • Interlocked.CompareExchange(_serverInfoState, 0, 0) 会强制刷新缓存,确保读取到内存中的最新值。
    • 示例中通过 GetCurrentState() 方法统一提供安全读取,避免直接访问变量。
  2. 原子操作的实现细节

    • 加 1 / 减 1:直接使用 Interlocked.Increment 和 Interlocked.Decrement,这两个方法是原子的且性能最优。
    • 加 N / 减 N:.NET 没有直接的原子加减 N 方法,通过 CompareExchange 配合循环实现(类似 CAS 操作),确保整个计算过程的原子性。
    • SubtractN 中增加了 Math.Max 保护,防止结果为负数(可根据业务需求调整)。
  3. 变量访问控制

    • 将 _serverInfoState 声明为 Private,避免外部直接修改,强制通过原子方法操作,减少线程安全隐患。
    • 提供 GetCurrentState() 作为唯一的读取入口,确保所有线程获取到一致的最新值。
  4. 使用场景

    • 既支持简单的开关状态(0/1),也支持计数场景(通过增减操作)。
    • 所有方法均为线程安全,可在多用户并发的 IIS 环境中安全使用。

通过这种方式,既能保持模块写法的简洁性,又能严格保证多线程环境下的操作安全性。


原子加N,这样代码太长了,效率行吗?会不会用LOCKOBJ更高效?

原子加 N 的实现代码(基于 CompareExchange 循环)虽然看起来较长,但在低冲突场景下效率通常优于 SyncLock(lock);而在高冲突场景下,SyncLock 可能更高效(避免大量无效自旋)。两者的取舍需结合实际并发强度判断。

1. 两种方式的效率对比

(1)基于 Interlocked.CompareExchange 的无锁实现(当前方式)vb
' 原子加N(无锁方式)
Public Function AddN(value As Integer) As Integer
    If value <= 0 Then Throw New ArgumentException("值必须为正数")
    Dim initial, updated As Integer
    Do
        initial = _serverInfoState
        updated = initial + value
    Loop While Interlocked.CompareExchange(_serverInfoState, updated, initial) <> initial
    Return updated
End Function
  • 优势
    无锁操作,避免了 SyncLock 的线程阻塞 / 唤醒开销(上下文切换成本)。当并发冲突较少时(多数情况下循环 1-2 次就会成功),效率极高。
  • 劣势
    冲突频繁时,会导致循环多次重试(自旋),浪费 CPU 资源;代码相对冗长。
(2)基于 SyncLock 的锁实现
' 定义专用锁对象
Private Shared ReadOnly _lockObj As New Object()

' 原子加N(锁方式)
Public Function AddN(value As Integer) As Integer
    If value <= 0 Then Throw New ArgumentException("值必须为正数")
    SyncLock _lockObj
        _serverInfoState += value
        Return _serverInfoState
    End SyncLock
End Function
  • 优势
    代码简洁,冲突频繁时更稳定(线程会进入等待队列,不消耗 CPU);适合复杂逻辑(如加 N 的同时需要其他操作)。
  • 劣势
    锁的获取 / 释放有固定开销(约几十纳秒到微秒级),低冲突场景下效率低于无锁方式;可能导致线程阻塞。

2. 如何选择?

  • 优先用无锁(CompareExchange 循环)的场景

    • 并发访问频率不高(冲突少),例如偶尔的计数更新。
    • 对延迟敏感(不希望线程阻塞),例如高频次的统计计数。
    • 操作逻辑简单(仅加减 N,无额外步骤)。
  • 优先用锁(SyncLock)的场景

    • 并发冲突频繁(多线程同时修改的概率高),例如秒杀场景的库存扣减。
    • 操作逻辑复杂(加 N 的同时需要读写其他变量、判断业务规则等)。
    • 希望代码更简洁易懂,降低维护成本。

3. 关于 “读取操作” 的补充

无论用哪种方式,多线程中绝对不能直接读取 _serverInfoState,必须通过安全方式获取最新值:

' 安全读取(无锁方式)
Public Function GetCurrentState() As Integer
    Return Interlocked.CompareExchange(_serverInfoState, 0, 0)
End Function

' 或(锁方式,需与修改操作共用同一把锁)
Public Function GetCurrentState() As Integer
    SyncLock _lockObj
        Return _serverInfoState
    End SyncLock
End Function

直接读取可能因 CPU 缓存导致 “脏读”(不同线程看到不同值),两种安全方式需与修改操作的同步机制匹配(无锁改则无锁读,锁改则锁读)。

总结

  • 低冲突、简单操作 → 无锁方式(CompareExchange 循环)更高效。
  • 高冲突、复杂操作 → 锁方式(SyncLock)更稳定。
  • 代码长度不直接决定效率,无锁方式的循环在多数场景下实际执行成本极低。

可根据实际并发压力测试结果选择,多数 Web 场景(中等并发)下,无锁方式是更优选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

专注VB编程开发20年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值