在 C# 中,除了lock语句之外,还有多种常用的锁机制,每种机制都有其特点和适用场景,以下为你详细介绍:

 

1. Monitor

  • 原理Monitor类是 C# 中用于实现同步的基础类,lock语句实际上是Monitor类的语法糖。Monitor类提供了EnterExit方法来获取和释放锁,还提供了TryEnter方法用于尝试获取锁而不阻塞线程。
  • 适用场景:当需要更灵活地控制锁的获取和释放,例如在获取锁之前进行一些额外的检查,或者在不同的代码块中释放锁时,可以使用Monitor类。
  • 示例代码
using System;
using System.Threading;

class Program
{
    private static readonly object _lockObj = new object();
    private static int _sharedResource = 0;

    static void Main()
    {
        Thread t1 = new Thread(Increment);
        Thread t2 = new Thread(Increment);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine($"Final value of shared resource: {_sharedResource}");
    }

    static void Increment()
    {
        bool lockTaken = false;
        try
        {
            Monitor.Enter(_lockObj, ref lockTaken);
            for (int i = 0; i < 100000; i++)
            {
                _sharedResource++;
            }
        }
        finally
        {
            if (lockTaken)
            {
                Monitor.Exit(_lockObj);
            }
        }
    }
}

 

2. Mutex

  • 原理Mutex(互斥体)是一种跨进程的同步原语,它可以确保在同一时间只有一个线程或进程可以访问共享资源。与Monitor不同,Mutex可以用于不同进程之间的同步。
  • 适用场景:适用于需要在多个进程之间同步访问共享资源的场景,例如多个进程同时访问一个文件或数据库资源。
  • 示例代码
using System;
using System.Threading;

class Program
{
    private static Mutex _mutex = new Mutex();
    private static int _sharedResource = 0;

    static void Main()
    {
        Thread t1 = new Thread(Increment);
        Thread t2 = new Thread(Increment);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine($"Final value of shared resource: {_sharedResource}");
    }

    static void Increment()
    {
        for (int i = 0; i < 100000; i++)
        {
            _mutex.WaitOne();
            try
            {
                _sharedResource++;
            }
            finally
            {
                _mutex.ReleaseMutex();
            }
        }
    }
}

 

3. SemaphoreSlim

  • 原理SemaphoreSlim是一个轻量级的信号量,它可以控制同时访问某个资源的线程数量。信号量维护一个计数器,当线程请求访问资源时,计数器减 1;当线程释放资源时,计数器加 1。当计数器为 0 时,后续请求访问的线程会被阻塞。
  • 适用场景:适用于需要限制并发访问数量的场景,例如数据库连接池、线程池等,确保同时访问资源的线程数量不超过一定限制。
  • 示例代码
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    private static SemaphoreSlim _semaphore = new SemaphoreSlim(2); // 最多允许2个线程同时访问
    private static int _sharedResource = 0;

    static async Task Main()
    {
        Task[] tasks = new Task[5];
        for (int i = 0; i < 5; i++)
        {
            tasks[i] = Task.Run(Increment);
        }

        await Task.WhenAll(tasks);

        Console.WriteLine($"Final value of shared resource: {_sharedResource}");
    }

    static async Task Increment()
    {
        await _semaphore.WaitAsync();
        try
        {
            for (int i = 0; i < 100000; i++)
            {
                _sharedResource++;
            }
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

 

4. ReaderWriterLockSlim

  • 原理ReaderWriterLockSlim允许多个线程同时进行读操作,但在进行写操作时会独占资源,不允许其他线程进行读或写操作。这种锁机制可以提高并发性能,因为读操作通常不会相互影响。
  • 适用场景:适用于读操作频繁,写操作较少的场景,例如缓存系统,多个线程可以同时读取缓存数据,但在更新缓存时需要独占资源。
  • 示例代码
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    private static ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
    private static int _sharedResource = 0;

    static async Task Main()
    {
        Task[] tasks = new Task[5];
        for (int i = 0; i < 3; i++)
        {
            tasks[i] = Task.Run(Read);
        }
        for (int i = 3; i < 5; i++)
        {
            tasks[i] = Task.Run(Write);
        }

        await Task.WhenAll(tasks);

        Console.WriteLine($"Final value of shared resource: {_sharedResource}");
    }

    static void Read()
    {
        _rwLock.EnterReadLock();
        try
        {
            Console.WriteLine($"Read value: {_sharedResource}");
        }
        finally
        {
            _rwLock.ExitReadLock();
        }
    }

    static void Write()
    {
        _rwLock.EnterWriteLock();
        try
        {
            _sharedResource++;
            Console.WriteLine($"Write new value: {_sharedResource}");
        }
        finally
        {
            _rwLock.ExitWriteLock();
        }
    }
}

 

5. SpinLock结构

  • 原理SpinLock是一种自旋锁,当线程尝试获取锁时,如果锁已经被其他线程持有,该线程会在一个循环中不断检查锁的状态,而不是进入阻塞状态。这种方式避免了线程上下文切换的开销,但会占用 CPU 资源。
  • 适用场景:适用于锁的持有时间非常短的场景,因为自旋等待可以避免线程上下文切换带来的性能开销。
  • 示例代码
using System;
using System.Threading;

class Program
{
    private static SpinLock _spinLock = new SpinLock();
    private static int _sharedResource = 0;

    static void Main()
    {
        Thread t1 = new Thread(Increment);
        Thread t2 = new Thread(Increment);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine($"Final value of shared resource: {_sharedResource}");
    }

    static void Increment()
    {
        bool lockTaken = false;
        for (int i = 0; i < 100000; i++)
        {
            try
            {
                _spinLock.Enter(ref lockTaken);
                _sharedResource++;
            }
            finally
            {
                if (lockTaken)
                {
                    _spinLock.Exit();
                    lockTaken = false;
                }
            }
        }
    }
}
C# 提供了多种锁机制,每种锁都有其适用场景。lock 语句适用于简单的同步场景;Monitor 类提供了更灵活的锁控制;Mutex 类适用于跨进程的同步;SemaphoreSlim 类适用于限制并发访问数量的场景。在实际开发中,需要根据具体的需求选择合适的锁机制,以确保程序的正确性和性能。