探索C#中的多线程神器Monitor和Lock

前言

    C#使用多线程时,涉及到线程安全问题时,需要开发人员特别注意。
Monitor、Lock是在 C# 中的多线程应用程序中提供线程安全的方法。两者都提供了一种机制来确保只有一个线程同时执行代码,以避免代码出现线程安全的问题,提高代码的稳定性。它们之间有紧密的联系,本文将通过案例介绍这两者的关系。.

1、Monitor

    Monitor是一个C#中System.Threading下的静态类,它提供了用于线程同步的方法和信号量。它们用来实现进入和退出临界区,以确保在同一时间只有一个线程可以访问共享资源。它有两个重要方法 Monitor.Enter和Monitor.Exit。

使用示例:

   public class MyClass    {        static readonly object lockObject = new object();        public void MonitorThead()        {           // 在 C#4.0以上版本,Monitor.Enter(_object,ref _lockTaken)           // 的重载函数获取独占锁和指定的对象,并自动设置一个值,指示是否已使用锁。            Monitor.Enter(lockObject);            try            {                int j = 0;                for (int i = 0; i < 10; i++)                {                    Thread.Sleep(100);                    Console.Write($"{j++},");                }                Console.WriteLine();
            }            finally            {                //在 C#4.0以上版本 这里判断                //if(_lockTaken)                //{                //    Monitor.Exit(lockObject);                //}                Monitor.Exit(lockObject);            }        }    }     internal class Program    {        static void Main(string[] args)        {            Thread[] Threads = new Thread[2];            for (int i = 0; i < 2; i++)            {                Threads[i] = new Thread(new ThreadStart(new MyClass().MonitorThead));                Threads[i].Name = "Name " + i;            }            foreach (Thread t in Threads)                t.Start();            //Console.WriteLine("Hello, World!");        }
    }

效果:

探索C#中的多线程神器Monitor和Lock

例子中我们使用Monitor来保护lockObject,使得在同一时刻只有一个线程try中的代码。如果有其他线程尝试进入会被阻塞,直到锁被释放。

2、Lock

    Lock想必经常使用多线程的程序员比较熟悉了。它是C#中的一个关键字,功能其实与Monitor类似。其实Lock就是Monitor类的语法糖,编译器会将lock关键字转换为对Monitor.Enter和Monitor.Exit的调用,让语法更加简洁。下面我们将用案例反编译后的中间语言看看它们的关系。

使用示例:

   public class MyClass    {        //private object lockObject = new object(); 使用更安全        static readonly object _object = new object();        public void LockThead()        {            lock (_object)            {                int j = 0;                for (int i = 0; i < 10; i++)                {                    Thread.Sleep(100);                    Console.Write($"{j++},");                }                Console.WriteLine();            }        }    }
      static void Main(string[] args)        {            Thread[] Threads = new Thread[2];            for (int i = 0; i < 2; i++)            {                Threads[i] = new Thread(new ThreadStart(new MyClass().LockThead));                Threads[i].Name = "Name " + i;            }            foreach (Thread t in Threads)                t.Start();          }          //欢迎关注公众号:DOTNET开发跳槽,领取海量面试题。//加微信号xbhpnet入群交流

反编译的部分代码:

探索C#中的多线程神器Monitor和Lock

以上是反编译IL代码的部分切图,从反编译IL的结果可以看出,Lock底层也用了Monitor。Lock效果跟上面的一样。只不过代码更加简洁美观。

注意Object对象的声明方式:

//由于是私有的,每一个实例都有自己对象,所以使用更安全和稳定private object lockObject = new object(); //static readonly 由于是静态只读,全局只用一个,相当于全局锁static readonly object _object = new object();

本案例为了展示效果使用了static readonly object。

结语

Monitor和Lock在多线程的使用上效果相同,不同是Lock更加简洁,没有特殊要求推荐大家使用lock。需要说明的是Monitor锁定代码区域后,可以使用 Monitor.Wait、Monitor.Pulse 和 Monitor.PulseAll 方法,在特定的条件下可以使用。