zoukankan      html  css  js  c++  java
  • C#多线程系列(2):多线程锁lock和Monitor

    C# 中,可以使用 lock 关键字和 Monitor 类来解决多线程锁定资源和死锁的问题。

    官方解释:lock 语句获取给定对象的互斥 lock,执行语句块,然后释放 lock。

    下面我们将来探究 lock 关键字和 Monitor 类的使用。

    1,Lock

    lock 用于读一个引用类型进行加锁,同一时刻内只有一个线程能够访问此对象。lock 是语法糖,是通过 Monitor 来实现的。

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

    实际上字符串也可以作为锁的对象使用,只是由于字符串对象的特殊性,可能会造成不同位置的不同线程冲突。
    如果你能保证字符串的唯一性,例如 Guid 生成的字符串,也是可以作为锁的对象使用的(但不建议)。
    锁的对象也不一定要静态才行,也可以通过类实例的成员变量,作为锁对象。

    lock 原型

    lock 是 Monitor 的语法糖,生成的代码对比:

    lock (x)
    {
        // Your code...
    }
    
    object __lockObj = x;
    bool __lockWasTaken = false;
    try
    {
        System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
        // Your code...
    }
    finally
    {
        if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
    }
    

    这里先不理会 Monitor,后面再说。

    lock 编写实例

    首先,如果像下面这样写的话,拉出去打 si 吧。

            public void MyLock()
            {
                object o = new object();
                lock (o)
                {
                // 
                }
            }
    

    下面编写一个简单的锁,示例如下:

        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 += i;
                        Console.WriteLine("Sum1");
                        Thread.Sleep(TimeSpan.FromSeconds(2));
                    }
                }
            }
    
            public static void Sum2()
            {
                sum = 0;
                lock (obj)
                {
                    for (int i = 0; i < 10; i++)
                    {
                        sum += 1;
                        Console.WriteLine("Sum2");
                        Thread.Sleep(TimeSpan.FromSeconds(2));
                    }
                }
            }
        }
    

    类将自己设置为锁, 这可以防止恶意代码对公共对象采用做锁。

    例如:

      public void Access()
        {
            lock(this) {}
         }
    

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

    10 种优化锁的性能方法: http://www.thinkingparallel.com/2007/07/31/10-ways-to-reduce-lock-contention-in-threaded-programs/

    2,Monitor

    此对象提供同步访问对象的机制;Monotor 是一个静态类型,其方法比较少,常用方法如下:

    操作 说明
    Enter, TryEnter 获取对象的锁。 此操作还标记关键节的开头。 其他任何线程都不能输入临界区,除非它使用不同的锁定对象执行临界区中的说明。
    Wait 释放对象的锁,以允许其他线程锁定并访问对象。 调用线程会等待另一个线程访问对象。 使用脉冲信号通知等待线程关于对象状态的更改。
    Pulse 、PulseAll 将信号发送到一个或多个等待线程。 信号通知等待线程:锁定对象的状态已更改,锁的所有者已准备好释放该锁。 正在等待的线程置于对象的就绪队列中,因此它可能最终接收对象的锁。 线程锁定后,它可以检查对象的新状态,以查看是否已达到所需的状态。
    Exit 释放对象的锁。 此操作还标记受锁定对象保护的临界区的结尾。

    怎么用呢

    下面是一个很简单的示例:

            private static object obj = new object();
            private static bool acquiredLock = false;
    		
    		public static void Test()
            {
                try
                {
                    Monitor.Enter(obj, ref acquiredLock);
                }
                catch { }
                finally
                {
                    if (acquiredLock)
                        Monitor.Exit(obj);
                }
            }
    

    Monitor.Enter 锁定 obj 这个对象,并且设置 acquiredLock 为 true,告诉别人 obj 已经被锁定。

    最后结束时,判断 acquiredLock ,释放锁,并设置 acquiredLock 为 false。

    解释一下

    临界区:指被某些符号包围的范围。例如 {} 内。

    Monitor 对象的 Enter 和 Exit 方法来标记临界区的开头和结尾。

    Enter() 方法获取锁后,能够保证只有单个线程能够使用临界区中的代码。使用 Monitor 类,最好搭配 try{...}catch{...}finally{...} 来使用,因为如果获取到锁但是没有释放锁的话,会导致其它线程无限阻塞,即发生死锁。

    一般来说,lock 关键字够用了。

    示例

    下面示范了多个线程如何使用 Monitor 来实现锁:

           private static object obj = new object();
            private static bool acquiredLock = false;
            static void Main(string[] args)
            {
                new Thread(Test1).Start();
                Thread.Sleep(1000);
                new Thread(Test2).Start();
            }
    
            public static void Test1()
            {
                try
                {
                    Monitor.Enter(obj, ref acquiredLock);
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine("Test1正在锁定资源");
                        Thread.Sleep(1000);
                    }
    
                }
                catch { }
                finally
                {
                    if (acquiredLock)
                        Monitor.Exit(obj);
                    Console.WriteLine("Test1已经释放资源");
                }
            }
            public static void Test2()
            {
                bool isGetLock = false;
                Monitor.Enter(obj);
                try
                {
                    Monitor.Enter(obj, ref acquiredLock);
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine("Test2正在锁定资源");
                        Thread.Sleep(1000);
                    }
    
                }
                catch { }
                finally
                {
                    if (acquiredLock)
                        Monitor.Exit(obj);
                    Console.WriteLine("Test2已经释放资源");
                }
            }
    

    设置获取锁的时效

    如果对象已经被锁定,另一个线程使用 Monitor.Enter 对象,就会一直等待另一个线程解除锁定。

    但是,如果一个线程发生问题或者出现死锁的情况,锁一直被锁定呢?或者线程具有时效性,超过一段时间不执行,已经没有了意义呢?

    我们可以通过 Monitor.TryEnter() 来设置等待时间,超过一段时间后,如果锁还没有释放,就会返回 false。

    改造上面的示例如下:

            private static object obj = new object();
            private static bool acquiredLock = false;
            static void Main(string[] args)
            {
                new Thread(Test1).Start();
                Thread.Sleep(1000);
                new Thread(Test2).Start();
            }
    
            public static void Test1()
            {
                try
                {
                    Monitor.Enter(obj, ref acquiredLock);
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine("Test1正在锁定资源");
                        Thread.Sleep(1000);
                    }
                }
                catch { }
                finally
                {
                    if (acquiredLock)
                        Monitor.Exit(obj);
                    Console.WriteLine("Test1已经释放资源");
                }
            }
            public static void Test2()
            {
                bool isGetLock = false;
                isGetLock = Monitor.TryEnter(obj, 500);
                if (isGetLock == false)
                {
                    Console.WriteLine("锁还没有释放,我不干活了");
                    return;
                }
                try
                {
                    Monitor.Enter(obj, ref acquiredLock);
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine("Test2正在锁定资源");
                        Thread.Sleep(1000);
                    }
                }
                catch { }
                finally
                {
                    if (acquiredLock)
                        Monitor.Exit(obj);
                    Console.WriteLine("Test2已经释放资源");
                }
            }
    

    对于锁的使用,还有很多高级复杂的技术,本文简单地介绍了 Lock 和 Monitor 的使用。

    随着教程的深入,会继续学习很多高级的使用方法。

  • 相关阅读:
    JavaScript之面向对象与原型笔记整理--------创建对象(1)
    PTA乙级 (*1030 完美数列 (25分))
    PAT乙级 (1033 旧键盘打字 (20分)(字母大小写转换、判断是否为大小写字母数字))
    PTA乙级 (*1040 有几个PAT (25分))
    PTA乙级 (1042 字符统计 (20分))
    PTA乙级 (1043 输出PATest (20分))
    PTA乙级 (1048 数字加密 (20分))
    PTA乙级 (1049 数列的片段和 (20分))
    PTA乙级 (1051 复数乘法 (15分))
    PTA乙级 (*1054 求平均值 (20分))
  • 原文地址:https://www.cnblogs.com/whuanle/p/12722853.html
Copyright © 2011-2022 走看看