码迷,mamicode.com
首页 > 其他好文 > 详细

简单的同步

时间:2015-08-25 21:47:58      阅读:166      评论:0      收藏:0      [点我收藏+]

标签:c#

简单的同步

 

前言

 

好几天没写写C#的代码了,突发奇想,还是写写吧,别手生了.还是继续写点关于线程的小东西吧,这次说说线程的同步吧.

 

 

 

看下面这一段代码

    

class ThreadTest
    {
        static int _val1 = 1, _val2 = 1;
        internal static void Go()
        {
            if (_val2!=0)
            {
                Console.WriteLine(_val1/_val2);
            }
            _val2 = 0;
        }
}
 

分析:乍一看,没问题啊,但是我要告诉你,这段代码是费线程安全的,假设两个线程AB,AB都执行到了Go()方法的if判断中,假设_val2=1.所以两个线程都通过了了if判断,线程A执行了CW语句,然后退出了if语句,执行_val2=0,此时val2=0,但是此时线程B才刚刚执行到CW方法,而此时_val2=0.所以你有可能会得到一个divide by zero的异常.

 

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

 

    class ThreadTest
    {
        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关键字实际上是Moitor.EnterMonitor.Exit的缩写.例如上面所写的代码和下面的等价:

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


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

 

还有,Monitor.Entertry方法之间可能会抛出异常.例如在线程上调用Abort,或者是OutOfMemoryException.

 

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

 

我们同样可以将上面的代码写成下面这样:

    class ThreadTest
    {
        static readonly object _locker = new object();
        static int _val1 = 1, _val2 = 1;
        internal static void Go()
        {
            
            bool lockTaken = false;
            try
            {
                Monitor.Enter(_locker,ref lockTaken);
                if (_val2 != 0)
                {
                    Console.WriteLine(_val1 / _val2);
                }
                _val2 = 0;
            }
            finally
            {
                if (lockTaken)
                {
                    Monitor.Exit(_locker);
                }
                
            }
 
        }
    }
 


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

 

 

 

选择同步对象

 

Monitor.Enter方法的参数是一个object类型,所以任何对象都可以是同步的,考虑下面的代码:
int i=5;	lock(i){}	//锁定值类型
lock(this){}	//锁定this对象
lock(typeof(Product)){}//锁定type对象
string str=”ddd”;	lock(str){}	//锁定字符串


 

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

 

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

 

由于字符串驻留机制,所以也不要锁定string.

 

 

 

嵌套锁

 

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

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


或者是

Monitor.Enter(locker);
Monitor.Enter(locker);
Monitor.Enter(locker);
//code
Monitor.Exit(locker);
Monitor.Exit(locker);
Monitor.Exit(locker);


 

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

        static readonly object _locker = new object();
        static void Main(string[] args)
        {
 
            lock (_locker)
            {
                Method();
            }
 
        }
        static void Method()
        {
            lock (_locker)
            {
                //code
            }
        }
 
 
 


死锁

 

先看这样的代码

static readonly object locker1 = new object();
        static readonly 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)//获取锁loccker2
            {
                Thread.Sleep(1000);//尝试获取locker1
                lock (locker1)
                {
                    Console.WriteLine("locker2     locker1");
                }
            }
        }


 

分析:在这里,主线程先获取locker2的锁,然后sleep,接着尝试获取locker1的锁.副线程先获取locker1的锁,然后sleep,接着尝试获取locker2的锁.程序进入死锁状态,两个线程都在等待对方释放自己等待的锁.

 

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

 

 

 

 小小的结一下

个人感觉那个死锁的案例,需要记住,记住代码

 

 

 

 

版权声明:本文为博主原创文章,未经博主允许不得转载。

简单的同步

标签:c#

原文地址:http://blog.csdn.net/shanyongxu/article/details/47982419

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!