C#常用的4种锁的使用以及解释

1、lock

 internal class Program
    {
        static object lockObject=new object();
        static void Main(string[] args)
        {
            Student student = new Student();
            Thread thread1 = new Thread(new ThreadStart(Start1));
            thread1.Start();

            Thread thread2 = new Thread(Start2);
            thread2.Start(student);
        }

        private static void Start2(object? obj)
        {
            var student = obj as Student;
            lock (lockObject)
            {
                for (int i = 0; i < 30; i++)
                {
                    Console.WriteLine(student!.Name + i);
                }
            }
        }

        private static void Start1()
        {     
            lock (lockObject)
            {
                for (int i = 0; i < 30; i++)
                {
                    Console.WriteLine(i);
                }
            }

        }

    }

lock锁用于同步线程对共享资源的访问,这里的共享资源是静态的Console.WriteLine,当多个线程共同使用它的时候,lock内部会有一个监听器,监听它有没有在其他地方使用,有的话,我这里先暂停,它的底层是一个互斥锁,是一个非的概念。
2、Monitor

 internal class Program
    {
        static object lockObject = new object();
        static void Main(string[] args)
        {
            Student student = new Student();
            Thread thread1 = new Thread(new ThreadStart(Start1));
            thread1.Start();

            Thread thread2 = new Thread(Start2);
            thread2.Start(student);
        }

        private static void Start2(object? obj)
        {
            var student = obj as Student;
            Monitor.Enter(lockObject);         //在指定对象获取排他锁
            try
            {
                for (int i = 0; i < 30; i++)
                {
                    Console.WriteLine("张三"+i);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message.ToString());

            }
            finally
            {
                Monitor.Exit(lockObject);       //释放锁
            }
        }

        private static void Start1()
        {
            for (int i = 0; i < 30; i++)
            {
                Console.WriteLine(i);
            }
        }

    }

    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public Student()
        {
            Name = "张三";
        }
    }

Monitor的底层实现同样依赖于操作系统的同步机制。在.NET中,Monitor是基于Windows操作系统的Win32 API中的WaitForSingleObject和ReleaseMutex函数实现的,或者在其他平台上使用相应的同步机制。

当调用Monitor.Enter时,如果对象的锁没有被其他线程持有,当前线程将获得锁并继续执行。如果锁已经被其他线程持有,当前线程将等待直到锁被释放。Monitor.Exit用于释放当前线程持有的锁,允许其他等待的线程尝试获取锁。

使用Monitor时,通常需要与try...finally结构一起使用,以确保即使在发生异常的情况下锁也能被正确释放。这是Monitor与lock语句的一个主要区别,因为lock语句会自动处理异常情况并释放锁。
3、Semaphore

  internal class Program
    {
        static Semaphore _semaphore;

        static int currentCount;       //当前线程信号量
       
        static RandomNumberGenerator randomNumber;
        static Random r;
        static void Main(string[] args)
        {
            r = new Random();
            randomNumber = RandomNumberGenerator.Create();
            byte[] buffer = new byte[1024];
            randomNumber.GetBytes(buffer);

          

            _semaphore =new Semaphore(1,3);
            for (int i = 0; i < 5; i++)
            {
                ThreadPool.QueueUserWorkItem(Done,i+1);
            }
            Console.ReadKey(true);
        }

        private static void Done(object? state)
        {
            int id = (int)state;
            PrintThreadStatus(id, "等待");
            _semaphore.WaitOne();
            PrintThreadStatus(id, "进入");
            PrintCount(1);
            Thread.Sleep(r.Next(1000));
            PrintThreadStatus(id, "退出");
            PrintCount(-1);
            _semaphore.Release();
        }

        //输出线程状态
        static void PrintThreadStatus(int id,string thread)
        {
            Console.WriteLine($"线程{id}:{thread}");
        }

        //修改并输出线程数量
        static void PrintCount(int add)
        {
            Interlocked.Add(ref currentCount, add);
            Console.WriteLine($"=> 信号量{Interlocked.Exchange(ref currentCount,currentCount)}");
        }
    }

锁住的对象:
在这个代码示例中,Semaphore并不是直接“锁住”某个对象,而是作为一种同步机制,用来控制同时访问某个资源(在这种情况下是临界区代码)的线程数量。Semaphore有两个重要的参数:当前信号量计数(currentCount)和最大信号量计数(maxCount)。在这个例子中,_semaphore被初始化为new Semaphore(1, 3),这意味着初始时只有一个信号量可用,但最多可以有三个线程同时持有信号量。

当一个线程调用_semaphore.WaitOne();时,如果信号量计数大于0,它将减少计数并继续执行。如果计数为0,则线程将被阻塞,直到其他线程调用_semaphore.Release();来增加信号量计数。这确保了在任何时刻,只有指定数量的线程能够进入受保护的代码区域。

使用锁(在这个例子中是Semaphore)可以使多线程按顺序执行下去,因为Semaphore限制了同时执行临界区代码的线程数量。当一个线程进入临界区并执行完毕后,它通过调用_semaphore.Release();来释放信号量,允许其他等待的线程进入。

锁的底层实现:
Semaphore的底层实现依赖于操作系统的同步机制。在.NET中,Semaphore是基于Windows API中的同步对象,如CreateSemaphore和ReleaseSemaphore,来实现的。这些API提供了一种机制,用于跨多个线程或进程同步对共享资源的访问。

当调用WaitOne方法时,如果信号量计数大于0,它会减少计数并允许线程继续执行。如果计数为0,线程将等待,直到其他线程调用Release方法来增加信号量计数。这个过程涉及到操作系统内核的调度和同步原语,如互斥锁(mutexes)、信号量(semaphores)或事件(events)。

Semaphore的实现细节可能会因操作系统而异,但其核心概念是一致的:控制对共享资源的访问,以避免竞争条件和确保数据一致性。

Semaphore用于控制对临界区的访问,确保最多只有三个线程可以同时执行Done方法中的代码。通过这种方式,Semaphore帮助维护了线程间的同步,并防止了潜在的竞态条件。
4、Mutext既然是跨进程基元同步

posted @ 2024-07-01 14:59  孤沉  阅读(172)  评论(0编辑  收藏  举报