C# 锁 Lock 、Monitor 、Mutex 、ReaderWriterLockSlim

发布时间 2023-08-28 10:49:47作者: 不争丶

一、互斥锁

1、Lock  语法糖

lock 用于读一个引用类型进行加锁,同一时刻内只有一个线程能够访问此对象。

Lock关键字实际上是一个语法糖,它将Monitor对象进行封装,给object加上一个互斥锁

Lock 锁定的对象,应该是静态的引用类型(字符串除外)。

private static readonly  object obj = new object();
lock(obj)
{
     //要执行的代码逻辑
}
//具体实例代码最下面

实际源码:

public class Lock
{
    private object lockObject = new object();

    public void SomeMethod()
    {
        System.Threading.Monitor.Enter(lockObject);
        try
        {
            // 互斥锁保护的代码块
            // 在这里执行需要同步的操作
        }
        finally
        {
            System.Threading.Monitor.Exit(lockObject);
        }
    }
}

注意:

  1. 锁可以阻止其它线程执行锁块(lock(o){})中的代码,当锁定时,其它线程必须等待锁中的线程执行完成并释放锁。
  2. C#的锁来确保在同一时间只有一个线程可以执行该方法
  3. 但是这可能会给程序带来性能影响。锁不太适合I/O场景,例如文件I/O,繁杂的计算或者操作比较持久的过程,会给程序带来很大的性能损失。

2、Monitor

更灵活

Enter, TryEnter

获取对象锁。此操作同样会标记临界区的开头。其他任何线程都不能进入临界区,除非它使用其他锁定对象执行临界区中的指令。

Wait

释放对象上的锁以便允许其他线程锁定和访问该对象。在其他线程访问对象时,调用线程将等待。脉冲信号用于通知等待线程有关对象状态的更改。

Pulse (信号), PulseAll

向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更改,并且锁的所有者准备释放该锁。等待线程被放置在对象的就绪队列中以便它可以最后接收对象锁。一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。

Exit

释放对象上的锁。此操作还标记受锁定对象保护的临界区的结尾。


3、Mutex

为避免发送多线程发生死锁,Mutex的WaitOne()和ReleaseMutex()需成对配合使用。

using System;
using System.Threading;

namespace 锁
{
    class Program
    {
        private static Mutex mutex = new Mutex();
        static void Main(string[] args)
        {

            Thread[] thread = new Thread[3];
            for (int i = 0; i < 3; i++)
            {
                thread[i] = new Thread(ThreadMethod1);//方法引用
                thread[i].Name = "Thread-" + (i + 1).ToString();
            }
            for (int i = 0; i < 3; i++)
            {
                thread[i].Start();
            }
            Console.ReadKey();
        }


        public static void ThreadMethod1(object val)
        {
            mutex.WaitOne();    //获取锁
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine("{0}循環了{1}次", Thread.CurrentThread.Name, i);
            }
            mutex.ReleaseMutex();  //释放锁
        }
    }
}
注释后

二、读写锁

ReaderWriterLockSlim

1、对于同一把锁、多个线程可同时进入读模式。
2、对于同一把锁、同时只允许一个线程进入写模式。
3、对于同一把锁、同时只允许一个线程进入可升级的读模式。
4、通过默认构造函数创建的读写锁是不支持递归的,若想支持递归 可通过构造 ReaderWriterLockSlim(LockRecursionPolicy) 创建实例。
5、对于同一把锁、同一线程不可两次进入同一锁状态(开启递归后可以)
6、对于同一把锁、即便开启了递归、也不可以在进入读模式后再次进入写模式或者可升级的读模式(在这之前必须退出读模式)。
7、再次强调、不建议启用递归。
8、读写锁具有线程关联性,即两个线程间拥有的锁的状态相互独立不受影响、并且不能相互修改其锁的状态。
9、升级状态:在进入可升级的读模式 EnterUpgradeableReadLock后,可在恰当时间点通过EnterWriteLock进入写模式。
10、降级状态:可升级的读模式可以降级为读模式:即在进入可升级的读模式EnterUpgradeableReadLock后, 通过首先调用读取模式EnterReadLock方法,然后再调用 ExitUpgradeableReadLock 方法。

基本规则: 读读不互斥 读写互斥 写写互斥

            //写锁

            try
            {
                LockSlim.EnterWriteLock();

                //todo

            }
            catch (Exception ex)
            {
            }
            finally
            {
                LockSlim.ExitWriteLock();
            }

            //读锁

            try
            {
                LockSlim.EnterReadLock();

            }
            catch (Exception ex)
            {
            }
            finally
            {
                LockSlim.ExitReadLock();
            }

LOCK 用例

using System;
using System.Threading;

namespace 锁
{
    class Program
    {
        private static object obj = new object();
        private static int sum = 0;

        static void Main(string[] args)
        {
            Thread thread1 = new Thread(Sum1);
            thread1.Start();
            Thread thread2 = new Thread(Sum2);
            thread2.Start();

            while (true)
            {
                Console.WriteLine($"{DateTime.Now.ToString()}:"+sum);
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }
        }


        public static void Sum1()
        {

            sum = 0;
            lock (obj)
            {
                for (int i = 0; i < 10; i++)
                {
                    sum++;
                    Console.WriteLine("Sum1");
                    Thread.Sleep(TimeSpan.FromSeconds(2));
                }

            }
        }


        public static void Sum2()
        {

            sum = 0;
            lock (obj)
            {
                for (int i = 0; i < 10; i++)
                {
                    sum++;
                    Console.WriteLine("Sum2");
                    Thread.Sleep(TimeSpan.FromSeconds(2));
                }

            }
        }
    }
}