900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 深入浅出多线程系列之四:简单的同步 lock

深入浅出多线程系列之四:简单的同步 lock

时间:2021-03-19 02:21:36

相关推荐

深入浅出多线程系列之四:简单的同步 lock

1: 考虑下下面的代码:

classThreadUnsafe

{

staticint_val1=1,_val2=1;

internalstaticvoidGo()

{

if(_val2!=0)

{

Console.WriteLine(_val1/_val2);

}

_val2=0;

}

}

这段代码是非线程安全的,假设有两个线程A,B,A,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关键字,例如:

staticreadonlyobject_locker=newobject();

staticint_val1=1,_val2=1;

internalstaticvoidGo()

{

lock(_locker)

{

if(_val2!=0)

{

Console.WriteLine(_val1/_val2);

}

_val2=0;

}

}

此时线程A,B都只能有一个可以获得_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。

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

boollockTaken=false;

try

{

Monitor.Enter(_locker,reflockTaken);

//Dosomething..

}

finally{

if(lockTaken){

Monitor.Exit(_locker);

}

}

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

2:选择同步对象。

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

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

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

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

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

3:嵌套锁:

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

lock(locker)

lock(locker)

lock(locker)

{

//dosomething

}

或者是:

Monitor.Enter(locker);Monitor.Enter(locker);Monitor.Enter(locker);

//Dosomething.

Monitor.Exit(locker);Monitor.Exit(locker);Monitor.Exit(locker);

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

staticreadonlyobject_locker=newobject();

staticvoidMain()

{

lock(_locker)

{

AnotherMethod();

}

}

staticvoidAnotherMethod()

{

lock(_locker){//dosomething;}

}

4:死锁:

先看下面的代码:

staticobjectlocker1=newobject();

staticobjectlocker2=newobject();

publicstaticvoidMainThread()

{

newThread(()=>

{

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一样,它没有自动检测死锁机制,也不会结束一个线程来破坏死锁。死锁的线程会导致部分线程无限的等待。

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

参考资料:

/threading/

CLR Via C# 3.0

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。