zoukankan      html  css  js  c++  java
  • 深入浅出多线程系列之四:简单的同步 lock

    1: 考虑下下面的代码:

    class ThreadUnsafe
        {
            
    static int _val1 = 1, _val2 = 1;

            
    internal static void Go()
            {
                
    if (_val2 != 0)
                {
                    Console.WriteLine(_val1 
    /_val2);
                }
                _val2 
    = 0;
            }
        }

    这段代码是非线程安全的,假设有两个线程A,BA,B都执行到了Go方法的if判断中,假设_val2=1.所以两个线程A,B都通过if判断,

    A执行了Console.WriteLine方法,然后退出if语句,执行_val2=0,此时_val2=0.

    但是此时线程B才刚刚执行到Console.WriteLine方法,而此时_val2=0.所以你有可能会得到一个divide by zero 的异常。

     

    为了保证线程安全,我们可以使用Lock关键字,例如:

            static readonly object _locker = new object();
            
    static int _val1 = 1, _val2 = 1;

            
    internal static void Go()
            {
                
    lock (_locker)
                {
                    
    if (_val2 != 0)
                    {
                        Console.WriteLine(_val1 
    / _val2);
                    }
                    _val2 
    = 0;
                }
            }

    此时线程AB都只能有一个可以获得_locker锁,所以只能有一个线程来执行lock块的代码。

    C#Lock关键字实际上是Monitor.Enter,Monitor.Exit的缩写。例如上面的代码和下面的等价。

                Monitor.Enter(_locker);
                
    try
                {
                    
    if (_val2 != 0)
                    {
                        Console.WriteLine(_val1 
    / _val2);
                    }
                    _val2 
    = 0;
                }
                
    finally { Monitor.Exit(_locker); }

    如果在调用Monitor.Exit之前没有调用Monitor.Enter,则会抛出一个异常。

     

    不知道大家注意到没有,Monitor.Enter Try 方法之间可能会抛出异常。

    例如在线程上调用Abort,或者是OutOfMemoryException

    为了解决这个问题CLR 4.0提供了Monitor.Enter的重载,增加了lockTaken 字段,当Monitor.Enter成功获取锁之后,lockTaken就是True,否则为False

    我们可以将上面的代码改成下面的版本。

                bool lockTaken = false;
                
    try
                {
                    Monitor.Enter(_locker, 
    ref lockTaken);
                    
    // Do something..
                }
                
    finally
                    
    if(lockTaken) {
                        Monitor.Exit(_locker);
                    }
                }

    Monitor也提供了TryEnter方法,并且可以传递一个超时时间。如果方法返回True,则代表获取了锁,否则为false

    2:选择同步对象。

    Monitor.Enter方法的参数是一个object类型,所以任何对象都可以是同步对象,考虑下下面的代码:

    1. int i=5; lock(i){}         //锁定值类型
    2. lock(this){}           //锁定this对象
    3. lock(typeof(Product)){}      //锁定type对象。
    4. string str="dddd"; lock(str){}   //锁定字符串

    1:锁定值类型会将值类型进行装箱,所以Monitor.Enter进入的是一个对象,但是Monitor.Exit()退出的是另一个不同的对象。

    23:锁定thistype对象,会导致无法控制锁的逻辑,并且它很难保证不死锁和频繁的阻塞,在相同进程中锁定type对象会穿越应用程序域。

    4:由于字符串驻留机制,所以也不要锁定string,关于这点,请大家去逛一逛老A的博客。

    3:嵌套锁:

    同一个线程可以多次锁定同一对象。例如

    lock(locker)
        
    lock(locker)
            
    lock(locker)
            {
                
    // do something
         }

    或者是:

    Monitor.Enter(locker); Monitor.Enter(locker); Monitor.Enter(locker);
    //Do something.
    Monitor.Exit(locker); Monitor.Exit(locker); Monitor.Exit(locker);

    当一个线程使用一个锁调用另一方法的时候,嵌套锁就非常的有用。例如:

           static readonly object _locker = new object();

            
    static void Main()
            {
                
    lock (_locker)
                { 
                    AnotherMethod();
                }
            }

            
    static void AnotherMethod()
            {
                
    lock (_locker){ //dosomething;}
            }

    4:死锁:

    先看下面的代码:

            static object locker1 = new object();
            
    static object locker2 = new object();

            
    public static void MainThread()
            {
                
    new Thread(() =>
                    {
                        
    lock (locker1)   //获取锁locker1
                        {
                            Thread.Sleep(
    1000);
                            
    lock (locker2) //尝试获取locker2
                            {
                                Console.WriteLine(
    "locker1,locker2");
                            }
                        }
                    }).Start();
                
    lock (locker2) //获取锁locker2
                {
                    Thread.Sleep(
    1000);
                    
    lock (locker1) //尝试获取locker1
                    {
                        Console.WriteLine(
    "locker2,locker1");
                    }
                }
            }

    在这里

    主线程先获取locker2的锁,然后sleep,接着尝试获取locker1的锁。

    副线程先获取locker1的锁,然后sleep,接着尝试获取locker2的锁。

    程序进入了死锁状态,两个线程都在等待对方释放自己等待的锁。 

    CLR作为一个独立宿主环境,它不像SQL Server一样,它没有自动检测死锁机制,也不会结束一个线程来破坏死锁。死锁的线程会导致部分线程无限的等待。

     

    下篇文章会介绍一些其他同步构造。

     

    参考资料:

    http://www.albahari.com/threading/

    CLR Via C# 3.0

  • 相关阅读:
    Wintellect的Power Collections库
    rabbitMQ的几种工作模式
    解决死锁问题
    项目#editormd 的使用
    spring cloud篇#1
    科学#老鼠和毒药
    #杂记#实现一个简单的tomcat
    #栈#leetcode856.括号的分数
    #栈#单调栈#leetCode94.验证栈序列
    #树#遍历#LeetCode37.序列化二叉树
  • 原文地址:https://www.cnblogs.com/LoveJenny/p/2053604.html
Copyright © 2011-2022 走看看