CLR系列:由一段代码所想到的

      CLR系列有些时间没更新了,主要时由于最近一个项目太忙。所以没能更新。以后会继续。其他话就不说了,我们来看看一段代码:

 1 class TestWorker
 2 {        
 3     public void DoMultiThreadedWork(object someParameter)
 4     {
 5         lock (lockObject)
 6         {
 7             //  lots of work 
 8         }
 9     }
10 
11     private string lockObject = "lockit";
12 

这段代码很简单,大家看看这段代码有什么问题呢?

 

开始这么一看似乎没什么大的问题。可是仔细分析一下代码你就可以知道其中还是有一些很严重的问题的。假如你对Lock机制有所了解并且对string类型有过研究的话你就会发现出问题:

lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围。在上例中,锁的范围限定为此函数,因为函数外不存在任何对该对象的引用。严格地说,提供给 lock 的对象只是用来唯一地标识由多个线程共享的资源,所以它可以是任意类实例。然而,实际上,此对象通常表示需要进行线程同步的资源。例如,如果一个容器对象将被多个线程使用,则可以将该容器传递给 lock,而 lock 后面的同步代码块将访问该容器。只要其他线程在访问该容器前先锁定该容器,则对该对象的访问将是安全同步的。通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。关于Lock被编译器编译过后的代码请参考我的另一篇文章:Inside C#

StringCLR中有两个重要的属性:不变性和字符串驻留。这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。

所以锁定字符串尤其危险。

下面我们来看看字符串驻留是个怎样的东东。看看下面的代码

 1              string a = "lockit";  
 2              string b = "lockit"
 3              string c = "LOCKIT".ToLower();  
 4              string d = "lock" + "it"
 5              string e1 = "lock";
 6              string e2 = "it";
 7              string ee = e1 + "it";
 8              string e = e1 + e2; 
 9              Console.WriteLine(object.ReferenceEquals(a, b));
10              Console.WriteLine(object.ReferenceEquals(a, c));
11              Console.WriteLine(object.ReferenceEquals(a, d));
12              Console.WriteLine(object.ReferenceEquals(a, ee));
13              Console.WriteLine(object.ReferenceEquals(a, e));

 下边是输出的结果:

True,False,True,False,False

我们知道CLR处理每个对象在内存的时候都会额外的生成一个syncblockindex空间,这个就是用于对象同步用的。是大家平时开发使用最多的一个类型,MS的CLR部门为了简化操作和性能的优化做了两点处理,一是将string的创建过程简单化。一般的对象在创建的时候通过new关键字来实现;而string不需要这么做,我们只需要把对应的字符换赋给给对应的字符串变量就可以了。那么他们在创建过程中使用的MSIL指令时不同的——一般的引用对象的创建是通过newobj这样一个IL指令来实现的,而创建一个字符串变量的IL指令则是ldstrload string)。其次就是为了考虑性能的提升和内存节约上,对于创建相同的字符串,一般不会为他们分别分配内存块,相反地,他们会共享一块内存。CLR内部维护一个HashTable,这HashTable维护者大部分创建的string。这个HashTableKey对应的相应的string本身,而Value则是分配给这个string的内存块的引用。我们知道在一个托管进程被创建以后,在托管进程的内存空间里面,包含了System DomainShared Domain等等,而这个HashTable就是放在System Domain里,所以它是在这整个程序的生命周期里都是存在的和被共享的。一般地,在程序运行过程中,如果需要的创建一个stringCLR会根据这个stringHash Code试着在HashTable中找这个相同的string,如果找到,则直接把找到的string的地址赋给相应的变量,如果没有则在托管堆中创建一个stringCLR会先在managed heap中创建该strng,并在HashTable中创建一个Key-Value,Key为这个string本身,Valuestring的内存地址,这个地址最重被赋给响应的变量。

上面的例子后面两个为False告诉我们对于对一个动态创建的字符串,驻留机制便不会起作用。这种情况产生的MSIL也不一样。但是我们可以用System.String中的静态方法Intern来解决。

通过以上的分析,回到第一个问题,这段代码会出现什么问题我们就知道了。这可以说明平时工作过程中大家尽量小心自己的代码,可以从一些小的代码看到.NET内部的实现机制以及平时自己写代码过程中的疏忽。

参考文章:http://msdn.microsoft.com/zh-cn/library/ms173179(VS.80).aspx

CLR继续中。。大家说说希望看到关于CLR什么方面的,看看能不能写写。我不能写,可以叫些专家写写来满足大家。

版权所有归"布衣软件工作者".未经容许不得转载.

posted on 2008-10-10 14:18 gjcn 阅读(2576) 评论(25)  编辑 收藏 网摘 所属分类: .Net Framework

评论

#1楼  2008-10-10 09:44 梦在天涯      

lockObject 必须是reference类型把!   回复  引用  查看    

#2楼  2008-10-10 09:44 梦在天涯      

string这个特殊的class估计是有问题啊   回复  引用  查看    

#3楼  2008-10-10 09:45 飞林沙      

看过留名,学习了.........
  回复  引用  查看    

#4楼 [楼主] 2008-10-10 09:46 gjcn      

@梦在天涯
你说的对。   回复  引用  查看    

#5楼  2008-10-10 09:46 飞林沙      

@梦在天涯

是的,但是string也是reference类型啊,呵呵 ,只不过是特殊的reference罢了   回复  引用  查看    

#6楼  2008-10-10 10:14 John Rambo      

Tricky.
从来没想过用string做lock。   回复  引用  查看    

#7楼  2008-10-10 10:17 bushi ba [未注册用户]

脑子 被驴踢了才去锁定字符串,程序里面的相同的字符串就是一个对象   回复  引用    

#8楼  2008-10-10 10:33 Anders Liu      

7L有点意思。。。   回复  引用  查看    

#9楼  2008-10-10 10:52 非主流程序员      

我一般用lock(this)不知道对不对啊?   回复  引用  查看    

#10楼  2008-10-10 10:58 hahahehe [未注册用户]

@非主流程序员
不行啊,有时候会有问题   回复  引用    

#11楼  2008-10-10 11:41 玄天尊的小屋      

@hahahehe
是不是this要看this的对象类型?需要reference类型???

PS:a,b是不是引用同一内存地址呢?   回复  引用  查看    

#12楼  2008-10-10 11:52 iMax Shi      

我是把 private string lockObject = "lockit";
换成了private object lockObject;
应该没问题吧?   回复  引用  查看    

#13楼  2008-10-10 12:31 Robin1986      

@iMax Shi
private object lockObject = new object();   回复  引用  查看    

#14楼  2008-10-10 13:12 Ivony...      

没看懂楼主在说啥。

1、string类型是不可修改的,也就是实例线程安全的,所以lock不lock没有什么区别。

2、如果要锁定一个字段,必须锁定这个字段所在的对象,如果要锁定一个静态字段,则应该使用lock ( typeof ( TypeName ) )的语法。这个在lock的说明中再明白无误。   回复  引用  查看    

#15楼  2008-10-10 13:12 overred      

@非主流程序员
看你的this是否被其他资源占用了。。
否则也会产生死锁

最好锁单一object   回复  引用  查看    

#16楼  2008-10-10 13:56 xiao_p      

framework design guidelines 上面明确的说
lock 最好不要 lock string 类型

!!!   回复  引用  查看    

#17楼  2008-10-10 14:22 飞林沙      

我认为一般情况来说还是声明一个辅助对象好一些   回复  引用  查看    

#18楼  2008-10-10 14:35 !A.Z [未注册用户]

从来都没有看到过这样的代码,看到了也只能拜一拜,lz文章的意义不大。
可能刚从院校里出来的新力军都有惊人的创造力...   回复  引用    

#19楼  2008-10-10 14:58 装配脑袋      

@Ivony...
typeof是不适合lock的,因为别人也可以typeof那个类型,得到的和你是一个实例,很容易就把你的线程锁住。封装性就是要避免这种事情发生。我认为一般情况一个private字段,object类型,值为new object()是最好不过的lock对象。   回复  引用  查看    

#20楼 [楼主] 2008-10-10 15:01 gjcn      

@装配脑袋
嗯,大致我也是这么认为的,有没有对CLR的建议啊。写些什么知识点呢   回复  引用  查看    

#21楼  2008-10-10 15:15 狼Robot      

private static object Locker = new object();
private static AnyClass _Instance;

public static AnyClass Instance
{
get
{
if(_Instance == null)
{
lock(Locker)
{
if(_Instance == null)
{
_Instance = new AnyClass();
}
}
}
return _Instance;
}
}   回复  引用  查看    

#22楼  2008-10-10 17:00 飘遥(周振兴)      

正如@Ivony...所说,string是线程安全的
一般用new一个object   回复  引用  查看    

#23楼  2008-10-11 10:40 Cheese      

lock string的时侯,这个string应该是有意义的值,类似一个业务主键,如果相同的key,就要进行互斥;如果仅仅是"lockit",那就没什么意义了,而且会带来楼主说的问题。   回复  引用  查看    

#24楼  2008-10-13 09:42 Ivony...      

--引用--------------------------------------------------
装配脑袋: @Ivony...
typeof是不适合lock的,因为别人也可以typeof那个类型,得到的和你是一个实例,很容易就把你的线程锁住。封装性就是要避免这种事情发生。我认为一般情况一个private字段,object类型,值为new object()是最好不过的lock对象。
--------------------------------------------------------
我明白你的意思了,lock一个字段然后在所有的方法中使用这个字段。这样就可以确保方法不会被同时调用,因为一个方法被调用时 这个字段的实例被锁定了。同时有其他方法调用时就会因为这个字段进入临界区而被阻塞。   回复  引用  查看    

#25楼  2008-11-12 09:47 simon_cm [未注册用户]

学习   回复  引用    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
Google站内搜索
[推荐职位]上海盛大网络招聘架构师



China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
近千种 9-95 新二手计算图书火热销售中!
开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》

相关文章:

相关链接:
 

导航

公告

These postings are provided "AS IS" with no warranties, and confer no rights.
<2008年10月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

统计

与我联系

搜索

 

常用链接

留言簿

我参与的团队

随笔分类

随笔档案

文章分类

友情BLOG

最新评论

阅读排行榜

评论排行榜