首页
产品与方案
众成服务
走进众成
新闻中心
企业文化
联系我们
解决方案
众成软件
维护支持
运维服务
技术交流
公司介绍
荣誉资质
合作伙伴
招贤纳士
公司新闻
业界动态
文化建设
企业文化
荣誉榜
首页
>
产品与方案
>
众成软件
众成软件
多线程中lock用法的经典实例
作者:众成 文章来源:软件部 点击数: 更新时间:2019-04-26 14:14:29
一、
Lock
定义
lock
关键字可以用来确保代码块完成运行,而不会被其他线程中断。它可以把一段代码定义为互斥段(
critical section
),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。这是通过在代码块运行期间为给定对象获取互斥锁来实现的。
在多线程中,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。
而在
.NET
中最好了解一下进程、应用域和线程的概念,因为
Lock
是针对线程一级的,而在
.NET
中应用域是否会对
Lock
起隔离作用,我的猜想是,即不在同一应用域中的线程无法通过
Lock
来中断;另外也最好能了解一下数据段、代码段、堆、栈等概念。
在
C# lock
关键字定义如下:
lock
(
expression
)
statement_block
,其中
expression
代表你希望跟踪的对象,通常是对象引用。
如果你想保护一个类的实例,一般地,你可以使用
this
;如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。
而
statement_block
就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。
二、简单解释一下执行过程
先来看看执行过程,代码示例如下:
private static object ojb = new object();
lock(obj)
{
//
锁定运行的代码段
}
假设线程
A
先执行,线程
B
稍微慢一点。线程
A
执行到
lock
语句,判断
obj
是否已申请了互斥锁,判断依据是逐个与已存在的锁进行
object.ReferenceEquals
比较
(
此处未加证实
)
,如果不存在,则申请一个新的互斥锁,这时线程
A
进入
lock
里面了。
这时假设线程
B
启动了,而线程
A
还未执行完
lock
里面的代码。线程
B
执行到
lock
语句,检查到
obj
已经申请了互斥锁,于是等待
;
直到线程
A
执行完毕,释放互斥锁,线程
B
才能申请新的互斥锁并执行
lock
里面的代码。
三、
Lock
的对象选择问题
接下来说一些
lock
应该锁定什么对象。
1
、为什么不能
lock
值类型
比如
lock(1)
呢
?lock
本质上
Monitor.Enter
,
Monitor.Enter
会使值类型装箱,每次
lock
的是装箱后的对象。
lock
其实是类似编译器的语法糖,因此编译器直接限制住不能
lock
值类型。退一万步说,就算能编译器允许你
lock(1)
,但是
object.ReferenceEquals(1,1)
始终返回
false(
因为每次装箱后都是不同对象
),
也就是说每次都会判断成未申请互斥锁,这样在同一时间,别的线程照样能够访问里面的代码,达不到同步的效果。同理
lock((object)1)
也不行。
2
、
Lock
字符串
那么
lock("xxx")
字符串呢
?MSDN
上的原话是:
锁定字符串尤其危险,因为字符串被公共语言运行库
(CLR)“
暂留
”
。
这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。
3
、
MSDN
推荐的
Lock
对象
通常,最好避免锁定
public
类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则
lock(this)
可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型
(
相比于对象
)
也可能导致问题。
而且
lock(this)
只对当前对象有效,如果多个对象之间就达不到同步的效果。
而自定义类推荐用私有的只读静态对象,比如:
private static readonly object obj = new object();
为什么要设置成只读的呢
?
这时因为如果在
lock
代码段中改变
obj
的值,其它线程就畅通无阻了,因为互斥锁的对象变了,
object.ReferenceEquals
必然返回
false
。
4
、
lock(typeof(Class))
与锁定字符串一样,范围太广了。
五、特殊问题:
Lock
(
this
)等的详细解释
在以前编程中遇到
lock
问题总是使用
lock(this)
一锁了之,出问题后翻看
MSDN
突然发现下面几行字:通常,应避免锁定
public
类型,否则实例将超出代码的控制范围。常见的结构
lock (this)
、
lock (typeof (MyType))
和
lock ("myLock")
违反此准则:如果实例可以被公共访问,将出现
C# lock this
问题。如果
MyType
可以被公共访问,将出现
lock (typeof (MyType))
问题。由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现
lock(“myLock”)
问题。
来看看
C# lock this
问题
:
如果有一个类
Class1
,该类有一个方法用
lock(this)
来实现互斥:
1. publicvoidMethod2()
2. {
3. lock(this)
4. {
5. System.Windows.Forms.MessageBox.Show("Method2End");
6. }
7. }
如果在同一个
Class1
的实例中
,
该
Method2
能够互斥的执行。但是如果是
2
个
Class1
的实例分别来执行
Method2,
是没有互斥效果的。因为这里的
lock,
只是对当前的实例对象进行了加锁。
Lock(typeof(MyType))
锁定住的对象范围更为广泛,由于一个类的所有实例都只有一个类型对象(该对象是
typeof
的返回结果),锁定它,就锁定了该对象的所有实例,微软现在建议,不要使用
lock(typeof(MyType)),
因为锁定类型对象是个很缓慢的过程,并且类中的其他线程、甚至在同一个应用程序域中运行的其他程序都可以访问该类型对象,因此,它们就有可能代替您锁定类型对象,完全阻止您的执行,从而导致你自己的代码的挂起。
锁住一个字符串更为神奇,只要字符串内容相同,就能引起程序挂起。原因是在
.NET
中,字符串会被暂时存放,如果两个变量的字符串内容相同的话,
.NET
会把暂存的字符串对象分配给该变量。所以如果有两个地方都在使用
lock(“my lock”)
的话,它们实际锁住的是同一个对象。到此,微软给出了个
lock
的建议用法:锁定一个私有的
static
成员变量。
.NET
在一些集合类中(比如
ArrayList,HashTable
,
Queue
,
Stack
)已经提供了一个供
lock
使用的对象
SyncRoot
,用
Reflector
工具查看了
SyncRoot
属性的代码,在
Array
中,该属性只有一句话
:return this,
这样和
lock array
的当前实例是一样的。
ArrayList
中的
SyncRoot
有所不同
1. get
2. {
3. if(this._syncRoot==null)
4. {
5. Interlocked.CompareExchange(refthis._syncRoot,newobject(),null);
6. }
7. returnthis._syncRoot;
其中
Interlocked
类是专门为多个线程共享的变量提供原子操作(如果你想锁定的对象是基本数据类型,那么请使用这个类),
CompareExchange
方法将当前
syncRoot
和
null
做比较,如果相等,就替换成
new object()
,这样做是为了保证多个线程在使用
syncRoot
时是线程安全的。集合类中还有一个方法是和同步相关的:
Synchronized
,该方法返回一个对应的集合类的
wrapper
类,该类是线程安全的,因为他的大部分方法都用
lock
来进行了同步处理,比如
Add
方法:
1. publicoverridevoidAdd(objectkey,objectvalue)
2. {
3. lock(this._table.SyncRoot)
4. {
5. this._table.Add(key,value);
6. }
7. }
这里要特别注意的是
MSDN
提到:从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合:
1. QueuemyCollection=newQueue();
2. lock(myCollection.SyncRoot){
3. foreach(ObjectiteminmyCollection){
4. //Insertyourcodehere.
5. }
6. }
最后
注意:应避免锁定
public
类型,否则实例将超出代码的控制范围。常见的结构
lock (this)
、
lock (typeof (MyType))
和
lock ("myLock")
违反此准则:
1
)如果实例可以被公共访问,将出现
lock (this)
问题;
2
)如果
MyType
可以被公共访问,将出现
lock (typeof (MyType))
问题;
3
)由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现
lock("myLock")
问题;
最佳做法是定义
private
对象来锁定
,
或
private static
对象变量来保护所有实例所共有的数据。
六、参考资料
由于参考的资料都保存在本地,只能先列出标题,无法提供原文地址,深表歉意!
1
)描述
C#
多线程中
Lock
关键字
2
)解决
C# lock this
问题
3
)基于
C#
中的
lock
关键字的总结
4
)
C# lock
关键字
地址:温州市车站大道大诚商厦E幢四楼 | 电话:0577-88891333 | 技术服务电话:4008515159 | 传真:0577-88363999
邮箱:jucher@jucher.com |
浙ICP备05000620号-1
Copyright © 2009-2019 JUCHER CORPORATION CO., LTD All Rights Reserve