EasyText, EasyLicense 的作者, https://github.com/EasyHelper Good Good Study,Day Day Up.

 

深入浅出多线程系列之四:简单的同步 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

 

posted @ 2011-05-23 06:27  LoveJenny  阅读(5688)  评论(8编辑  收藏  举报
EasyText, EasyLicense 的作者, https://github.com/EasyHelper Good Good Study,Day Day Up.