C# 多线程环境下控制对共享资源访问的方法

0 阅读3分钟

前言

在多线程编程中,确保多个线程能够安全地访问共享资源是一个关键挑战。如果没有适当的同步机制,可能会导致数据不一致、竞态条件甚至是程序崩溃。C# 提供了多种方法来控制对共享资源的访问,每种方法都有其适用场景和优缺点。本文将详细介绍几种常见的同步机制,并通过代码示例展示它们的使用方法。

正文

一、Monitor

定义Monitor 是 C# 中最基本的同步机制,通过 EnterExit 方法来控制对共享资源的访问。它提供了排他锁的功能,确保任何时刻只有一个线程可以访问共享资源。

优点

  • 简单易用。

  • 适合粗粒度的同步控制。

缺点

  • 只能实现排他锁,不能实现读写锁。

  • 性能相对较低。

class Program
{
    static readonly object _lock = new object();
    static int _counter = 0;

    static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            new Thread(IncrementCounter).Start();
        }

        Console.ReadKey();
    }

    static void IncrementCounter()
    {
        Monitor.Enter(_lock);
        try
        {
            _counter++;
            Console.WriteLine($"Counter: {_counter}");
        }
        finally
        {
            Monitor.Exit(_lock);
        }
    }
}

二、Mutex

定义Mutex 是一个操作系统对象,用于进程间共享,通过 WaitOneReleaseMutex 来控制对共享资源的访问。它提供了跨进程的同步能力。

优点

  • 可跨进程使用。

  • 适合进程间的同步。

缺点

  • 相比 Monitor,性能开销较大,因为涉及到系统调用。
class Program
{
    static Mutex _mutex = new Mutex();
    static int _counter = 0;

    static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            new Thread(IncrementCounter).Start();
        }

        Console.ReadKey();
    }

    static void IncrementCounter()
    {
        _mutex.WaitOne();
        _counter++;
        Console.WriteLine($"Counter: {_counter}");
        _mutex.ReleaseMutex();
    }
}

三、ReaderWriterLockSlim

定义ReaderWriterLockSlim 实现了读写分离锁,允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。这种机制适用于读多写少的场景。

优点

  • 适合读多写少的场景,提高了并发性能。

缺点

  • 相对复杂,可能会引起死锁。

  • 不支持递归锁。

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

    static void Main()
    {
        for (int i = 0; i < 5; i++)
        {
            new Thread(ReadCounter).Start();
            new Thread(IncrementCounter).Start();
        }

        Console.ReadKey();
    }

    static void ReadCounter()
    {
        _rwLock.EnterReadLock();
        Console.WriteLine($"Counter: {_counter}");
        _rwLock.ExitReadLock();
    }

    static void IncrementCounter()
    {
        _rwLock.EnterWriteLock();
        _counter++;
        Console.WriteLine($"Counter incremented to: {_counter}");
        _rwLock.ExitWriteLock();
    }
}

四、Semaphore 和 SemaphoreSlim

定义SemaphoreSemaphoreSlim 都是信号量,用于控制同时访问共享资源的线程数量。通过 WaitOneRelease 方法,可以控制访问资源的线程数量。

优点

  • 可以控制多个线程同时访问共享资源的数量,灵活性较高。

缺点

  • 使用起来较为复杂,需要谨慎处理信号量的释放。
// Semaphore 示例
class Program
{
    static Semaphore _semaphore = new Semaphore(2, 2); // Allow 2 threads to access the resource simultaneously
    static int _counter = 0;

    static void Main()
    {
        for (int i = 0; i < 5; i++)
        {
            new Thread(IncrementCounter).Start();
        }

        Console.ReadKey();
    }

    static void IncrementCounter()
    {
        _semaphore.WaitOne();
        _counter++;
        Console.WriteLine($"Counter: {_counter}");
        _semaphore.Release();
    }
}

// SemaphoreSlim 示例
class Program
{
    static SemaphoreSlim _semaphore = new SemaphoreSlim(2, 2); // Allow 2 threads to access the resource simultaneously
    static int _counter = 0;

    static void Main()
    {
        for (int i = 0; i < 5; i++)
        {
            new Thread(IncrementCounter).Start();
        }

        Console.ReadKey();
    }

    static void IncrementCounter()
    {
        _semaphore.Wait();
        _counter++;
        Console.WriteLine($"Counter: {_counter}");
        _semaphore.Release();
    }
}

五、lock 关键字

定义lock 关键字用于在代码块级别实现互斥锁,确保共享资源在多线程环境中被安全访问。它是基于 Monitor 的概念实现的语法糖。

优点

  • 简单易用,避免了开发人员手动管理锁的复杂性。

  • 自动释放锁,避免了忘记释放锁导致死锁的情况。

缺点

  • 潜在死锁风险,如果在锁内部发生了阻塞操作。
  • 频繁使用可能导致性能开销。
class Program
{
    static readonly object _lock = new object();
    static int _counter = 0;

    static void Main()
    {
        for (int i = 0; i < 5; i++)
        {
            new Thread(IncrementCounter).Start();
        }

        Console.ReadKey();
    }

    static void IncrementCounter()
    {
        lock (_lock)
        {
            _counter++;
            Console.WriteLine($"Counter: {_counter}");
        }
    }
}

总结

在多线程环境中,选择合适的同步机制对于保证程序的正确性和性能至关重要。

Monitorlock 提供了简单易用的解决方案,适用于大多数场景;

Mutex 则更适合于跨进程的同步需求;

ReaderWriterLockSlim 对于读多写少的场景非常有效;

SemaphoreSemaphoreSlim 则为控制并发线程数量提供了灵活的选择。

理解这些同步机制的特点及其适用场景,可以帮助大家更好地设计和实现高效的多线程应用程序。

希望本文提供的内容能够帮助大家选择最适合项目的同步机制,并避免常见的多线程编程陷阱。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:我只吃饭不洗碗

出处:cnblogs.com/INetIMVC/p/18330485

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!