.NET Lock

.NET lock 语句实现原理详解

1. 概述

.NET 的 lock 语句是基于 Monitor 类实现的线程同步机制。它采用了 混合锁(Hybrid Lock) 的设计理念,结合了轻量级锁(Thin Lock)和重量级锁(Fat Lock),以在不同场景下提供最佳性能。

1.1 设计目标

  • 低开销:无竞争时几乎零开销
  • 可重入:支持同一线程多次获取同一锁
  • 公平性:避免线程饥饿,保证等待线程最终能获取锁
  • 异常安全:确保在异常情况下能正确释放锁

2. 核心架构

2.1 对象头结构(ObjHeader)

每个 .NET 对象都有一个对象头,用于存储同步信息:

class ObjHeader
{
private:
#ifdef HOST_64BIT
    DWORD    m_alignpad;           // 64位对齐填充
#endif
    Volatile<DWORD> m_SyncBlockValue;  // 同步块值(包含索引和标志位)
};

2.2 SyncBlockValue 位字段布局

// 位掩码定义
#define BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX    0x08000000  // 是否为哈希码或同步块索引
#define BIT_SBLK_IS_HASHCODE                0x04000000  // 是否为哈希码
#define BIT_SBLK_SPIN_LOCK                  0x10000000  // 自旋锁标志

// Thin Lock 布局(当 BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX 为 0)
#define SBLK_MASK_LOCK_THREADID             0x0000FFFF  // 线程ID (bits 0-15)
#define SBLK_MASK_LOCK_RECLEVEL             0x003F0000  // 递归级别 (bits 16-21)
#define SBLK_LOCK_RECLEVEL_INC              0x00010000  // 递归级别增量

位字段布局图:

31    28  26  25   22  21    16  15            0
|-----|--|--|----|---|-------|---|-------------|
|flags|sp|hb|hash| - |reclev |tid|  threadid  |
  • flags: 预留标志位
  • sp: 自旋锁位
  • hb: 哈希或同步块索引标志
  • hash: 哈希码标志
  • reclev: 递归级别(0-63)
  • threadid: 持有锁的线程ID(0-65535)

3. 三级锁机制

3.1 Thin Lock(轻量级锁)

特点:

  • 直接存储在对象头中
  • 支持 65535 个不同的线程ID
  • 支持最多 63 级递归
  • 无竞争时性能最佳

获取过程:

bool TryEnter_FastPath(object obj)
{
    DWORD oldValue = obj->GetHeader()->m_SyncBlockValue;
    
    // 检查是否为 thin lock 且未被持有
    if ((oldValue & (BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | SBLK_MASK_LOCK_THREADID)) == 0)
    {
        DWORD threadId = GetCurrentThreadId() & SBLK_MASK_LOCK_THREADID;
        DWORD newValue = oldValue | threadId;
        
        // 原子操作尝试获取锁
        if (InterlockedCompareExchange(&obj->GetHeader()->m_SyncBlockValue, newValue, oldValue) == oldValue)
        {
            return true; // 成功获取
        }
    }
    
    return false; // 需要走慢路径
}

递归处理:

// 检查是否为同一线程的递归调用
DWORD currentThreadId = GetCurrentThreadId() & SBLK_MASK_LOCK_THREADID;
if ((oldValue & SBLK_MASK_LOCK_THREADID) == currentThreadId)
{
    // 检查递归级别是否超限
    if ((oldValue & SBLK_MASK_LOCK_RECLEVEL) < SBLK_MASK_LOCK_RECLEVEL)
    {
        DWORD newValue = oldValue + SBLK_LOCK_RECLEVEL_INC;
        if (InterlockedCompareExchange(&m_SyncBlockValue, newValue, oldValue) == oldValue)
        {
            return true; // 递归成功
        }
    }
}

3.2 Fat Lock(重量级锁)

当发生以下情况时,会升级为 Fat Lock:

  • 发生锁竞争
  • 递归级别超过 63
  • 线程ID超过 65535
  • 调用 Wait/Pulse 方法

SyncBlock 结构:

class SyncBlock
{
    AwareLock m_Monitor;              // 实际的锁对象
    SLink     m_Link;                 // 用于线程等待队列
    DWORD     m_dwHashCode;           // 对象哈希码
    // ... 其他字段
};

class AwareLock
{
private:
    LockState m_lockState;            // 锁状态(原子操作)
    ULONG     m_Recursion;            // 递归计数
    DWORD     m_HoldingThreadId;      // 持有线程ID
    SIZE_T    m_HoldingOSThreadId;    // 操作系统线程ID
    CLREvent  m_SemEvent;             // 等待事件
};

3.3 锁升级过程

void UpgradeToFatLock(Object* obj)
{
    // 1. 分配 SyncBlock
    SyncBlock* syncBlock = SyncBlockCache::GetSyncBlock();
    
    // 2. 从 thin lock 状态迁移信息
    DWORD oldValue = obj->GetHeader()->m_SyncBlockValue;
    if (!(oldValue & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX))
    {
        DWORD threadId = oldValue & SBLK_MASK_LOCK_THREADID;
        DWORD recLevel = (oldValue & SBLK_MASK_LOCK_RECLEVEL) >> SBLK_RECLEVEL_SHIFT;
        
        if (threadId != 0) // 如果锁被持有
        {
            // 初始化 fat lock 为已持有状态
            syncBlock->m_Monitor.InitializeToLockedWithNoWaiters(
                recLevel + 1, threadId, GetOSThreadId(threadId));
        }
    }
    
    // 3. 原子地设置同步块索引
    DWORD syncBlockIndex = SyncBlockCache::GetIndex(syncBlock);
    obj->GetHeader()->SetIndex(syncBlockIndex | BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX);
}

4. Monitor 类实现

4.1 Enter 方法

public static void Enter(object obj)
{
    if (obj == null)
        throw new ArgumentNullException(nameof(obj));

    // 快路径:尝试获取 thin lock
    if (!TryEnter_FastPath(obj))
    {
        // 慢路径:处理竞争情况
        Enter_Slowpath(obj);
    }
}

快路径实现:

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool TryEnter_FastPath(object obj);

// Native 实现
bool TryEnter_FastPath(Object* obj)
{
    ObjHeader* objHeader = obj->GetHeader();
    
    for (int attempt = 0; attempt < 10; ++attempt)
    {
        DWORD oldValue = objHeader->m_SyncBlockValue.LoadWithoutBarrier();
        
        // 检查是否可以直接获取 thin lock
        if (CanAcquireThinLock(oldValue))
        {
            DWORD newValue = CalculateNewThinLockValue(oldValue);
            if (objHeader->m_SyncBlockValue.CompareExchange(newValue, oldValue) == oldValue)
            {
                return true;
            }
            // CAS 失败,重试
            continue;
        }
        
        // 无法获取 thin lock
        break;
    }
    
    return false;
}

4.2 Exit 方法

public static void Exit(object obj)
{
    if (obj == null)
        throw new ArgumentNullException(nameof(obj));

    LeaveHelperAction action = Exit_FastPath(obj);
    
    if (action != LeaveHelperAction.None)
    {
        Exit_Slowpath(action, obj);
    }
}

快路径释放:

LeaveHelperAction Exit_FastPath(Object* obj)
{
    ObjHeader* objHeader = obj->GetHeader();
    DWORD oldValue = objHeader->m_SyncBlockValue.LoadWithoutBarrier();
    
    // 检查是否为 thin lock
    if (!(oldValue & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX))
    {
        DWORD threadId = GetCurrentThreadId() & SBLK_MASK_LOCK_THREADID;
        
        // 验证当前线程是否持有锁
        if ((oldValue & SBLK_MASK_LOCK_THREADID) == threadId)
        {
            DWORD recLevel = oldValue & SBLK_MASK_LOCK_RECLEVEL;
            
            if (recLevel == 0)
            {
                // 完全释放锁
                DWORD newValue = oldValue & ~SBLK_MASK_LOCK_THREADID;
                if (objHeader->m_SyncBlockValue.CompareExchange(newValue, oldValue) == oldValue)
                {
                    return LeaveHelperAction.None; // 成功释放
                }
            }
            else
            {
                // 减少递归级别
                DWORD newValue = oldValue - SBLK_LOCK_RECLEVEL_INC;
                if (objHeader->m_SyncBlockValue.CompareExchange(newValue, oldValue) == oldValue)
                {
                    return LeaveHelperAction.None; // 成功递减
                }
            }
        }
        else
        {
            return LeaveHelperAction.Error; // 不是持有者
        }
    }
    
    return LeaveHelperAction.UseSlowPath; // 需要慢路径处理
}

5. AwareLock 实现

5.1 锁状态管理

class LockState
{
private:
    static const UINT32 IsLockedMask = 1 << 0;                    // 是否被锁定
    static const UINT32 ShouldNotPreemptWaitersMask = 1 << 1;    // 是否应避免抢占等待者
    static const UINT32 SpinnerCountMask = 0x7 << 2;             // 自旋线程计数
    static const UINT32 IsWaiterSignaledToWakeMask = 1 << 5;     // 是否有等待者被信号唤醒
    static const UINT32 WaiterCountMask = ~0x3F;                 // 等待者计数

private:
    UINT32 m_state;

public:
    bool IsLocked() const { return !!(m_state & IsLockedMask); }
    bool HasAnyWaiters() const { return m_state >= WaiterCountIncrement; }
    bool HasAnySpinners() const { return !!(m_state & SpinnerCountMask); }
};

5.2 自旋等待策略

void AwareLock::SpinWait(const YieldProcessorNormalizationInfo &normalizationInfo, DWORD spinIteration)
{
    // 自适应自旋策略
    if (spinIteration < g_SpinConstants.dwInitialDuration)
    {
        // 短暂自旋,不让出CPU
        YieldProcessor();
    }
    else if (spinIteration < g_SpinConstants.dwMaximumDuration)
    {
        // 中等自旋,偶尔让出CPU
        if ((spinIteration % 8) == 0)
        {
            __SwitchToThread();
        }
        else
        {
            YieldProcessor();
        }
    }
    else
    {
        // 长时间等待,完全让出CPU
        __SwitchToThread();
    }
}

5.3 等待队列管理

struct ThreadQueue
{
    // 将线程加入等待队列
    static void EnqueueThread(WaitEventLink *pWaitEventLink, SyncBlock *psb)
    {
        // 遍历到队列末尾(FIFO 策略)
        SLink *pPrior = &psb->m_Link;
        while (pPrior->m_pNext)
        {
            pPrior = pPrior->m_pNext;
        }
        
        // 将新等待者添加到末尾
        pPrior->m_pNext = &pWaitEventLink->m_LinkSB;
        pWaitEventLink->m_LinkSB.m_pNext = NULL;
    }
    
    // 从队列中移除头部线程
    static WaitEventLink *DequeueThread(SyncBlock *psb)
    {
        SLink *pLink = psb->m_Link.m_pNext;
        if (pLink)
        {
            psb->m_Link.m_pNext = pLink->m_pNext;
            return WaitEventLinkForLink(pLink);
        }
        return NULL;
    }
};

6. lock 语句的编译器转换

6.1 C# 代码

lock (obj)
{
    // 临界区代码
    DoSomething();
}

6.2 编译器生成的等价代码

object lockObj = obj;
bool lockTaken = false;
try
{
    Monitor.Enter(lockObj, ref lockTaken);
    // 临界区代码
    DoSomething();
}
finally
{
    if (lockTaken)
    {
        Monitor.Exit(lockObj);
    }
}

6.3 IL 代码分析

// lock 进入
ldloc.0      // 加载 obj
dup          // 复制引用
stloc.1      // 存储到 lockObj
ldloca.s 2   // 加载 lockTaken 地址
call Monitor.Enter(object, bool&)

// 临界区代码
// ... 用户代码 ...

// lock 退出(在 finally 块中)
ldloc.2      // 加载 lockTaken
brfalse.s EXIT
ldloc.1      // 加载 lockObj
call Monitor.Exit(object)
EXIT:

7. 性能优化技术

7.1 锁消除(Lock Elision)

JIT 编译器可以在某些情况下完全消除锁:

// 这个锁可能被消除,因为 obj 是局部对象
var obj = new object();
lock (obj)
{
    // 只有当前线程可以访问 obj
    localVariable++;
}

7.2 锁粗化(Lock Coarsening)

// 优化前:多个细粒度锁
lock (obj) { DoA(); }
lock (obj) { DoB(); }
lock (obj) { DoC(); }

// 优化后:单个粗粒度锁
lock (obj) 
{ 
    DoA(); 
    DoB(); 
    DoC(); 
}

7.3 偏向锁(Biased Locking)

在单线程场景下,thin lock 的获取和释放可以进一步优化:

// 记录最后获取锁的线程
DWORD lastLockingThread = obj->GetHeader()->GetLastLockingThread();
if (lastLockingThread == currentThreadId)
{
    // 快速递归获取,无需 CAS 操作
    obj->GetHeader()->IncrementRecursionLevel();
    return true;
}

8. 内存模型保证

8.1 获取-释放语义

// Monitor.Enter 提供获取语义
void Monitor::Enter()
{
    // ... 锁获取逻辑 ...
    
    // 确保临界区内的内存操作不会重排序到锁获取之前
    MemoryBarrier();
}

// Monitor.Exit 提供释放语义  
void Monitor::Exit()
{
    // 确保临界区内的内存操作不会重排序到锁释放之后
    MemoryBarrier();
    
    // ... 锁释放逻辑 ...
}

8.2 volatile 语义

对象头的 m_SyncBlockValue 字段使用 Volatile<DWORD> 确保:

  • 读操作具有获取语义
  • 写操作具有释放语义
  • 防止编译器和CPU的重排序

8.3 并发编程核心概念详解

8.3.1 什么是临界区(Critical Section)

临界区是指访问共享资源的代码段,在任意时刻只能被一个线程执行。临界区是并发控制的核心概念。

临界区的特性

class BankAccount
{
    private decimal _balance = 1000m;
    private readonly object _lock = new object();
    
    // 临界区:访问共享资源 _balance 的代码段
    public void Withdraw(decimal amount)
    {
        lock (_lock)  // 临界区开始
        {
            // 这整个代码块就是临界区
            if (_balance >= amount)
            {
                Console.WriteLine($"当前余额: {_balance}");
                Thread.Sleep(100); // 模拟处理时间
                _balance -= amount;
                Console.WriteLine($"提取 {amount},剩余余额: {_balance}");
            }
            else
            {
                Console.WriteLine("余额不足");
            }
        } // 临界区结束
    }
}

为什么需要临界区保护

// 没有临界区保护的危险代码
public void UnsafeWithdraw(decimal amount)
{
    // 竞态条件示例
    if (_balance >= amount)        // 线程1和线程2同时检查,都看到余额1000
    {
        Thread.Sleep(100);         // 模拟网络延迟
        _balance -= amount;        // 线程1: 1000-800=200, 线程2: 1000-600=400
    }                              // 结果:最后执行的线程覆盖了前一个线程的结果
}

// 可能的执行序列:
// 时间点1: 线程1检查余额1000 >= 800 ✓
// 时间点2: 线程2检查余额1000 >= 600 ✓  
// 时间点3: 线程1设置余额 = 200
// 时间点4: 线程2设置余额 = 400 (错误!覆盖了线程1的结果)

临界区的设计原则

class OptimizedBankAccount
{
    private decimal _balance = 1000m;
    private readonly object _lock = new object();
    
    public bool Withdraw(decimal amount)
    {
        // 原则1: 最小化临界区 - 预先准备数据
        bool canWithdraw;
        decimal newBalance;
        
        lock (_lock)
        {
            // 原则2: 临界区内只做必要的原子操作
            canWithdraw = _balance >= amount;
            if (canWithdraw)
            {
                newBalance = _balance - amount;
                _balance = newBalance;
            }
            else
            {
                newBalance = _balance;
            }
        }
        
        // 原则3: 日志和通知放在临界区外
        if (canWithdraw)
        {
            LogTransaction($"提取 {amount},剩余 {newBalance}");
            NotifyBalanceChanged(newBalance);
        }
        else
        {
            LogError($"提取失败,余额不足 {newBalance}");
        }
        
        return canWithdraw;
    }
}

8.3.2 volatile 与内存可见性

内存可见性指的是一个线程对共享变量的修改,能否及时被其他线程看到。

CPU 缓存导致的可见性问题

CPU架构下的内存层次:
        CPU1           CPU2
         |              |
    +----------+   +----------+
    | L1 Cache |   | L1 Cache |  <- 每个CPU核心私有
    +----------+   +----------+
         |              |
    +----------+   +----------+
    | L2 Cache |   | L2 Cache |  <- 可能共享或私有
    +----------+   +----------+
         |              |
    +------------------------+
    |      L3 Cache          |  <- 多核心共享
    +------------------------+
               |
    +------------------------+
    |      主内存(RAM)        |  <- 所有CPU共享
    +------------------------+

volatile 的作用机制

class VolatileExample
{
    // 没有 volatile 的变量
    private bool _stopRequested = false;
    
    // 使用 volatile 的变量
    private volatile bool _stopRequestedVolatile = false;
    
    // 问题场景:编译器/CPU优化导致的问题
    public void WorkerThread()
    {
        while (!_stopRequested)  // 可能被优化为 while(true)
        {
            DoWork();
            // 编译器可能认为 _stopRequested 在循环中不会改变
            // 因此优化为只读取一次,缓存在寄存器中
        }
    }
    
    // 解决方案:使用 volatile
    public void VolatileWorkerThread()
    {
        while (!_stopRequestedVolatile)  // 每次都从内存读取
        {
            DoWork();
            // volatile 确保每次都从主内存读取最新值
        }
    }
    
    public void Stop()
    {
        _stopRequested = true;           // 可能只写入CPU缓存
        _stopRequestedVolatile = true;   // 强制写入主内存
    }
}

volatile 在 .NET 中的实现

// .NET 中的 volatile 实现
public class VolatileOperations
{
    private int _counter;
    private volatile int _volatileCounter;
    
    // 普通读写
    public void NormalOperation()
    {
        _counter = 42;              // 可能被重排序
        int value = _counter;       // 可能读取缓存值
    }
    
    // volatile 读写
    public void VolatileOperation()
    {
        _volatileCounter = 42;      // 立即刷新到主内存
        int value = _volatileCounter; // 从主内存读取
    }
    
    // 等价的 Volatile 类方法
    public void ExplicitVolatileOperation()
    {
        Volatile.Write(ref _counter, 42);     // 显式 volatile 写
        int value = Volatile.Read(ref _counter); // 显式 volatile 读
    }
}

volatile 的语义保证

class VolatileSemantics
{
    private volatile bool _flag;
    private int _data;
    
    // 写线程
    public void Writer()
    {
        _data = 42;         // 1. 设置数据
        _flag = true;       // 2. 设置标志 (volatile写)
        
        // volatile 写保证:
        // - 所有在 volatile 写之前的操作都不会被重排序到之后
        // - volatile 写会立即刷新到主内存
    }
    
    // 读线程
    public void Reader()
    {
        if (_flag)          // 1. 检查标志 (volatile读)
        {
            int value = _data; // 2. 读取数据
            Console.WriteLine($"读取到数据: {value}");
        }
        
        // volatile 读保证:
        // - 所有在 volatile 读之后的操作都不会被重排序到之前
        // - volatile 读会从主内存获取最新值
        // - 如果看到 _flag = true,一定能看到 _data = 42
    }
}

8.3.3 内存操作重排序

内存操作重排序是编译器和CPU为了提高性能而改变指令执行顺序的优化技术。

重排序的类型

class ReorderingExample
{
    private int _a, _b;
    private bool _flag1, _flag2;
    
    // 编译器重排序示例
    public void CompilerReordering()
    {
        // 源代码顺序
        _a = 1;           // 操作1
        _b = 2;           // 操作2
        _flag1 = true;    // 操作3
        
        // 编译器可能重排序为:
        // _b = 2;        (操作2先执行,因为不依赖_a)
        // _a = 1;        
        // _flag1 = true;
    }
    
    // CPU重排序示例  
    public void CpuReordering()
    {
        // 即使编译器不重排序,CPU也可能重排序:
        _a = 1;           // 可能在Store Buffer中等待
        _flag1 = true;    // 可能先于_a写入内存
        
        // 其他CPU可能看到:_flag1 = true 但 _a 还是旧值
    }
}

重排序导致的问题

class ReorderingProblem
{
    private int _data;
    private volatile bool _ready;
    
    // 生产者线程
    public void Producer()
    {
        _data = 42;       // 1. 设置数据
        _ready = true;    // 2. 设置就绪标志
        
        // 问题:如果发生重排序,可能变成:
        // _ready = true;  (先设置标志)
        // _data = 42;     (后设置数据)
        
        // 这会导致消费者看到 ready=true 但 data 还是旧值
    }
    
    // 消费者线程
    public void Consumer()
    {
        while (!_ready)   // 等待数据就绪
        {
            Thread.Yield();
        }
        
        Console.WriteLine($"数据: {_data}"); // 可能读到旧值!
    }
}

内存屏障(Memory Barrier)

class MemoryBarrierExample
{
    private int _data;
    private bool _ready;
    
    // 使用内存屏障防止重排序
    public void ProducerWithBarrier()
    {
        _data = 42;                    // 1. 设置数据
        Thread.MemoryBarrier();        // 内存屏障:确保上面的写操作完成
        _ready = true;                 // 2. 设置标志
        
        // 内存屏障确保:
        // - 屏障前的所有内存操作都在屏障后的操作之前完成
        // - 防止编译器和CPU重排序
    }
    
    public void ConsumerWithBarrier()
    {
        while (!_ready)
        {
            Thread.Yield();
        }
        
        Thread.MemoryBarrier();        // 确保读取到最新的数据
        Console.WriteLine($"数据: {_data}");
    }
}

.NET 中的内存屏障类型

public static class MemoryBarriers
{
    // 完整内存屏障
    public static void FullBarrier()
    {
        Thread.MemoryBarrier();  // 阻止所有重排序
    }
    
    // 获取屏障(LoadLoad + LoadStore)
    public static void AcquireBarrier()
    {
        // 阻止读操作与后续读/写操作重排序
        // 相当于 volatile 读的语义
        Volatile.Read(ref _dummy);
    }
    
    // 释放屏障(LoadStore + StoreStore)  
    public static void ReleaseBarrier()
    {
        // 阻止前面的读/写操作与后续写操作重排序
        // 相当于 volatile 写的语义
        Volatile.Write(ref _dummy, 0);
    }
    
    private static int _dummy;
}

实际应用:双重检查锁定

class Singleton
{
    private static volatile Singleton _instance;
    private static readonly object _lock = new object();
    
    public static Singleton Instance
    {
        get
        {
            // 第一次检查(避免不必要的锁)
            if (_instance == null)
            {
                lock (_lock)
                {
                    // 第二次检查(确保线程安全)
                    if (_instance == null)
                    {
                        // 创建实例的操作可能被重排序:
                        // 1. 分配内存
                        // 2. 调用构造函数
                        // 3. 赋值给_instance
                        
                        // 如果没有volatile,可能重排序为:
                        // 1. 分配内存  
                        // 2. 赋值给_instance (未初始化的对象!)
                        // 3. 调用构造函数
                        
                        _instance = new Singleton(); // volatile确保正确顺序
                    }
                }
            }
            
            return _instance;
        }
    }
    
    private Singleton() { /* 初始化代码 */ }
}

高级示例:无锁数据结构

class LockFreeStack<T>
{
    private volatile Node _head;
    
    private class Node
    {
        public T Data;
        public volatile Node Next;
        
        public Node(T data) { Data = data; }
    }
    
    public void Push(T item)
    {
        var newNode = new Node(item);
        
        while (true)
        {
            Node currentHead = _head;     // volatile 读
            newNode.Next = currentHead;   // 设置新节点的下一个节点
            
            // 关键:CAS操作具有完整的内存屏障语义
            if (Interlocked.CompareExchange(ref _head, newNode, currentHead) == currentHead)
            {
                break; // 成功更新头节点
            }
            
            // 失败则重试(其他线程修改了头节点)
        }
    }
    
    public bool TryPop(out T result)
    {
        while (true)
        {
            Node currentHead = _head;     // volatile 读
            
            if (currentHead == null)
            {
                result = default(T);
                return false;
            }
            
            Node newHead = currentHead.Next; // 获取新的头节点
            
            // CAS操作确保原子性和内存可见性
            if (Interlocked.CompareExchange(ref _head, newHead, currentHead) == currentHead)
            {
                result = currentHead.Data;
                return true;
            }
            
            // 失败则重试
        }
    }
}

8.3.4 总结与最佳实践

概念关系图

并发控制层次结构:

应用层面:
┌─────────────────┐
│   临界区保护     │ <- lock, Monitor, Mutex等
│   (Critical     │
│   Section)      │
└─────────────────┘
        ↓
内存模型层面:
┌─────────────────┐
│  获取-释放语义   │ <- volatile, Interlocked等
│  (Acquire-      │
│   Release)      │  
└─────────────────┘
        ↓
硬件层面:
┌─────────────────┐
│   内存屏障      │ <- CPU指令,防止重排序
│  (Memory        │
│   Barrier)      │
└─────────────────┘

选择合适的同步机制

class SynchronizationGuidelines
{
    // 场景1: 保护复杂的临界区
    private readonly object _lock = new object();
    private int _complexState1, _complexState2;
    
    public void UpdateComplexState()
    {
        lock (_lock) // 推荐:使用lock保护复杂操作
        {
            _complexState1 = CalculateNewValue1();
            _complexState2 = CalculateNewValue2();
            ValidateConsistency();
        }
    }
    
    // 场景2: 简单的标志变量
    private volatile bool _stopRequested;
    
    public void RequestStop()
    {
        _stopRequested = true; // 推荐:volatile用于简单标志
    }
    
    // 场景3: 原子计数器
    private int _counter;
    
    public void IncrementCounter()
    {
        Interlocked.Increment(ref _counter); // 推荐:原子操作
    }
    
    // 场景4: 单次初始化
    private static readonly Lazy<Resource> _resource = 
        new Lazy<Resource>(() => new Resource()); // 推荐:Lazy<T>
    
    public static Resource GetResource()
    {
        return _resource.Value;
    }
}

性能考虑

// 性能对比(大致数量级)
class PerformanceComparison
{
    // 最快:无同步
    public void NoSync() { } // ~1 纳秒
    
    // 很快:volatile 读写  
    private volatile int _vol;
    public void VolatileOp() { _vol = 42; } // ~2-5 纳秒
    
    // 快:原子操作
    private int _atomic;
    public void AtomicOp() { Interlocked.Increment(ref _atomic); } // ~10-50 纳秒
    
    // 中等:无竞争的lock
    private readonly object _lock = new object();
    public void LockOp() { lock(_lock) { } } // ~50-200 纳秒
    
    // 慢:有竞争的lock
    public void ContentedLock() { /* 竞争情况 */ } // ~1-100 微秒
    
    // 很慢:内核对象(Mutex等)
    public void KernelSync() { /* Mutex操作 */ } // ~1-10 毫秒
}

通过理解这些核心概念,您可以更好地设计和调试多线程程序,选择合适的同步机制来保证程序的正确性和性能。

9. 性能特征

9.1 时间复杂度

场景 Thin Lock Fat Lock
无竞争获取 O(1) O(1)
无竞争释放 O(1) O(1)
竞争获取 升级到 Fat Lock O(1) + 等待时间
递归获取 O(1) O(1)

9.2 内存开销

锁类型 内存开销 说明
Thin Lock 0 字节 直接存储在对象头中
Fat Lock ~200 字节 SyncBlock + AwareLock + 相关结构

9.3 性能基准

无竞争场景(典型):
- Thin Lock: ~1-2 CPU 周期
- Fat Lock: ~10-20 CPU 周期

竞争场景:
- 自旋等待: ~100-1000 CPU 周期  
- 内核等待: ~10,000+ CPU 周期

10. 最佳实践

10.1 锁对象选择

// ✅ 推荐:使用专门的锁对象
private readonly object _lock = new object();

public void Method()
{
    lock (_lock)
    {
        // 临界区代码
    }
}

// ❌ 不推荐:锁定字符串常量
lock ("string literal") { } // 可能导致死锁

// ❌ 不推荐:锁定 Type 对象
lock (typeof(MyClass)) { } // 影响范围太广

// ❌ 不推荐:锁定 this
lock (this) { } // 外部代码可能也锁定同一对象

10.2 避免常见陷阱

// ❌ 错误:锁定可变引用
object lockObj = GetLockObject();
lock (lockObj) { } // lockObj 可能被修改

// ✅ 正确:锁定不变引用
private readonly object _lock = new object();
lock (_lock) { }

// ❌ 错误:在锁内等待
lock (_lock)
{
    Task.Delay(1000).Wait(); // 长时间占用锁
}

// ✅ 正确:最小化锁持有时间
var data = PrepareData();
lock (_lock)
{
    UpdateSharedState(data); // 快速更新
}

10.3 死锁避免

// 使用一致的锁顺序
private static readonly object _lock1 = new object();
private static readonly object _lock2 = new object();

// ✅ 总是按相同顺序获取锁
public void Method1()
{
    lock (_lock1)
    {
        lock (_lock2)
        {
            // 工作代码
        }
    }
}

public void Method2()
{
    lock (_lock1) // 相同的顺序
    {
        lock (_lock2)
        {
            // 工作代码
        }
    }
}

11. 与其他同步原语的比较

11.1 vs Mutex

特性 lock/Monitor Mutex
跨进程
性能
可重入
异常安全 ✅(with using)

11.2 vs ReaderWriterLockSlim

特性 lock/Monitor ReaderWriterLockSlim
读写分离
实现复杂度
读操作性能 高(无写入时)
写操作性能

11.3 vs SemaphoreSlim

特性 lock/Monitor SemaphoreSlim
资源计数 1 N
异步支持
性能开销

12. 调试和诊断

12.1 死锁检测

// 使用 ConcurrentExclusiveSchedulerPair 检测死锁
var scheduler = new ConcurrentExclusiveSchedulerPair();

// 在调试版本中添加锁超时
#if DEBUG
    if (!Monitor.TryEnter(_lock, TimeSpan.FromSeconds(30)))
    {
        throw new TimeoutException("可能发生死锁");
    }
#else
    Monitor.Enter(_lock);
#endif

12.2 性能分析

// 测量锁竞争
private long _lockContentions = 0;

public void Method()
{
    if (!Monitor.TryEnter(_lock, 0))
    {
        Interlocked.Increment(ref _lockContentions);
        Monitor.Enter(_lock);
    }
    
    try
    {
        // 临界区代码
    }
    finally
    {
        Monitor.Exit(_lock);
    }
}

13. 总结

.NET 的 lock 语句是一个精心设计的同步机制,它通过以下关键技术实现了高性能:

  • 混合锁设计:从轻量级 thin lock 开始,必要时升级为重量级 fat lock
  • 对象头优化:巧妙利用对象头空间存储锁信息
  • 自适应策略:根据竞争情况动态调整自旋和等待策略
  • 内存模型保证:提供正确的获取-释放语义
  • 编译器优化:支持锁消除和锁粗化等优化技术

这些设计使得 lock 语句成为 .NET 中最常用且最可靠的同步原语,为多线程编程提供了简单而高效的解决方案。

posted @ 2025-08-18 14:57  MadLongTom  阅读(36)  评论(0)    收藏  举报