C#线程(四、简单的同步)

转自:见证大牛成长之路(http://blog.youkuaiyun.com/shanyongxu/article/details/47982419),本文为本人学习用,请点击链接查看原文,尊重楼楼大大版权。


简单的同步


看下面这一段代码:

class ThreadTest  
    {  
        static int _val1 = 1, _val2 = 1;  
        internal static void Go()  
        {  
            if (_val2!=0)  
            {  
                Console.WriteLine(_val1/_val2);  
            }  
            _val2 = 0;  
        }  
}  
分析:乍一看,没问题啊,但是我要告诉你,这段代码是 费线程安全的,假设两个线程A和B,A和B都执行到了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;  
        }  
  
    }  
}  
分析:此时两个线程A和B都只能有一个可以获得_locker锁,所以只能有一个线程来执行lock块的代码.
 
C#的lock关键字实际上是Moitor.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.
 
我们同样可以将上面的代码写成下面这样:

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()退出的是另一个不同的对象.
 
锁定this和type对象,会导致无法控制锁的逻辑,并且它很难保证不死锁和频繁的阻塞,在相同进程中锁定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一样,他没有自动检测死锁机制,也不会结束一个线程来破坏死锁.死锁的线程会导致部分线程无限的等待.
 
 
 小结一下
个人感觉那个死锁的案例,需要记住,记住代码。


再次声明:
转自:见证大牛成长之路(http://blog.youkuaiyun.com/shanyongxu/article/details/47982419),本文为本人学习用,请点击链接查看原文,尊重楼楼大大版权。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值