关于线程同步(转载)
上次写了个帖子记录了一些在UI开发上的关于异步调用的收获,上次的问题之所以用到异步调用就是因为需要开启一个工作线程来做一些事情,并保证线程安全。问题解决之后就开始在SDK,MSDN,和我的一个高手朋友的blog里找寻一些有关于线程同步问题的内容,看了一些东西,也自己写了一些东西测了一下,还是了解了一些原来概念上不是很清楚的内容。
上次用到需要同步的对象是一个Hashtable,采用的是简单到不能再简单的加锁的办法。但是,每次处理都要加锁未免太费事了,有没有完全线程安全的对象呢?在SDK中我找到了答案。通过Hashtable.Synchronized()方法,可以包装一个Hashtable的同步版本供我们使用,具体是如何实现的同步版本,通过Reflector进行一下察看就很清楚了,在这个方法中New了一个新对象并返回:
而这个新对象是一个派生自hashtalbe的类(SyncHashtable)对象,在这个类中,.net已经对所有的属性和方法进行了加锁,这样一来我们在使用这个对象的时候就不必自己再去作加锁的工作了。当然了这个类是一个内部的私有类,我们无法通过new的方式创建,只能通过Synchronized方法来获取。
这样一来就又有了一个问题,那是不是我们每声明一个Hashtable,都用Synchronized方法来包装一下呢?当然不是,首先对于单线程来说同步是没有意义的,其次即使是多线程程序如果我们能够非常确定hashtalbe的读取与写入不会产生冲突,那我们也没有必要去同步它,毕竟加锁和解锁也是有性能损耗的。如果只是简单的在一次使用中需要同步仅仅采用手动的加锁就ok了,但是如果多处用到了这个Hashtable,那么在声明的时候就进行一次同步包装还是有必要的,毕竟人的记忆力是有限的,说不定什么地方就由于没有进行同步处理而出错。
还有一个问题也请教了朋友找到了答案。就是对于加锁的对象的选取。我加锁的对象就是Hashtable这个对象本身,但是在SDK中却看到了这样的代码:
Hashtable myCollection = new Hashtable();
lock( myCollection.SyncRoot ) {
foreach ( Object item in myCollection )
{
// Insert your code here.
}
SyncRoot是何许人也?SDK中给出的定义是:获取可用于同步对Hashtable的访问的对象。上面整了一个返回同步包装,这又来了一个同步访问的对象。有点晕。不,那是相当的晕。这个时候Reflector又再次显示了它无可替代了威力。
首先找到Hashtable,然后就是SyncRoot属性,不看不要紧,一看更傻了:
返回自己?什么概念?最要命的是SDK有这么一段话:
"同步代码必须在 Hashtable 的 SyncRoot 上执行操作,而不是直接在 Hashtable 上执行操作。这确保了从其他对象派生的集合的正确操作。特别是,它维护了与其他线程的正确同步,这些线程可能同时正在修改该 Hashtable 对象。"
不能对自身加锁,但是却通过对返回对象自身的一个属性加锁,初学者应该能够体会我当时的无助与愤怒。但是我马上从抓狂的状态下恢复了回来,将自己的精神从对一家叫做微微有点软的公司的愤慨中转回到问题本身。公司的名字虽然叫微软,但是做的东西向来还算坚挺(大部分,而且要求不要过于苛刻),因此我很快的意识到问题还是出在了我自身(每次都不愿意接受这个残酷的事实)。难道仅仅是为了和其他实现了ICollection的集合类在加锁的时候形成统一的形式,当然也应该有这个原因,但绝非核心因素,又向老朋友求助了(脸皮还真是厚啊,我们俩不是一个数量级的,不知道每次被我问在他看来不算问题的问题是什么感受?鄙视?我忍,求知无罪)通过他的解释和又查阅了一些资料,初步对加锁的对象有了一些认识。
首先,加锁的对象必须是引用类型的,这无可厚非。
其次,实际上对于加锁的对象只要是个引用类型的对象(并不需要是需要同步的对象本身,甚至可以new一个object对象来用)就可以满足最基本线程同步的要求。看来在这里我犯了一个现在看来很傻的错误,想当然的认为同步哪个对象就需要锁哪个对象,事实又一次证明了我的想象力的丰富在某些时候并不是什么优点。
最后,关于SyncRoot属性的使用我朋友的解释是,当我们需要对对象的同步访问进行一些自定义的控制是(例如:对于集合的读取器我们不加锁,只对编写器代码进行对象的加锁),使用对象的属性就可以在属性的实现代码中进行控制,而直接锁定对象本身是无法实现对同步访问的控制的。
明白了不少东西,但是想在自己的实际工作中应用起来可能还需要一些时间,毕竟我线在的任务涉及的大量线程的是没有的,即使有,再线程同步的时候也需要加小心,毕竟无意义的加锁是会使系统变得很慢,当变得相当的慢的时候,相信用户体验就和死锁没有什么区别了。
上次用到需要同步的对象是一个Hashtable,采用的是简单到不能再简单的加锁的办法。但是,每次处理都要加锁未免太费事了,有没有完全线程安全的对象呢?在SDK中我找到了答案。通过Hashtable.Synchronized()方法,可以包装一个Hashtable的同步版本供我们使用,具体是如何实现的同步版本,通过Reflector进行一下察看就很清楚了,在这个方法中New了一个新对象并返回:
public static Hashtable Synchronized(Hashtable table) { if (table == null) { throw new ArgumentNullException("table"); } return new Hashtable.SyncHashtable(table); } |
而这个新对象是一个派生自hashtalbe的类(SyncHashtable)对象,在这个类中,.net已经对所有的属性和方法进行了加锁,这样一来我们在使用这个对象的时候就不必自己再去作加锁的工作了。当然了这个类是一个内部的私有类,我们无法通过new的方式创建,只能通过Synchronized方法来获取。
这样一来就又有了一个问题,那是不是我们每声明一个Hashtable,都用Synchronized方法来包装一下呢?当然不是,首先对于单线程来说同步是没有意义的,其次即使是多线程程序如果我们能够非常确定hashtalbe的读取与写入不会产生冲突,那我们也没有必要去同步它,毕竟加锁和解锁也是有性能损耗的。如果只是简单的在一次使用中需要同步仅仅采用手动的加锁就ok了,但是如果多处用到了这个Hashtable,那么在声明的时候就进行一次同步包装还是有必要的,毕竟人的记忆力是有限的,说不定什么地方就由于没有进行同步处理而出错。
还有一个问题也请教了朋友找到了答案。就是对于加锁的对象的选取。我加锁的对象就是Hashtable这个对象本身,但是在SDK中却看到了这样的代码:
Hashtable myCollection = new Hashtable();
lock( myCollection.SyncRoot ) {
foreach ( Object item in myCollection )
{
// Insert your code here.
}
SyncRoot是何许人也?SDK中给出的定义是:获取可用于同步对Hashtable的访问的对象。上面整了一个返回同步包装,这又来了一个同步访问的对象。有点晕。不,那是相当的晕。这个时候Reflector又再次显示了它无可替代了威力。
首先找到Hashtable,然后就是SyncRoot属性,不看不要紧,一看更傻了:
public virtual object SyncRoot { get { return this; } } |
"同步代码必须在 Hashtable 的 SyncRoot 上执行操作,而不是直接在 Hashtable 上执行操作。这确保了从其他对象派生的集合的正确操作。特别是,它维护了与其他线程的正确同步,这些线程可能同时正在修改该 Hashtable 对象。"
不能对自身加锁,但是却通过对返回对象自身的一个属性加锁,初学者应该能够体会我当时的无助与愤怒。但是我马上从抓狂的状态下恢复了回来,将自己的精神从对一家叫做微微有点软的公司的愤慨中转回到问题本身。公司的名字虽然叫微软,但是做的东西向来还算坚挺(大部分,而且要求不要过于苛刻),因此我很快的意识到问题还是出在了我自身(每次都不愿意接受这个残酷的事实)。难道仅仅是为了和其他实现了ICollection的集合类在加锁的时候形成统一的形式,当然也应该有这个原因,但绝非核心因素,又向老朋友求助了(脸皮还真是厚啊,我们俩不是一个数量级的,不知道每次被我问在他看来不算问题的问题是什么感受?鄙视?我忍,求知无罪)通过他的解释和又查阅了一些资料,初步对加锁的对象有了一些认识。
首先,加锁的对象必须是引用类型的,这无可厚非。
其次,实际上对于加锁的对象只要是个引用类型的对象(并不需要是需要同步的对象本身,甚至可以new一个object对象来用)就可以满足最基本线程同步的要求。看来在这里我犯了一个现在看来很傻的错误,想当然的认为同步哪个对象就需要锁哪个对象,事实又一次证明了我的想象力的丰富在某些时候并不是什么优点。
最后,关于SyncRoot属性的使用我朋友的解释是,当我们需要对对象的同步访问进行一些自定义的控制是(例如:对于集合的读取器我们不加锁,只对编写器代码进行对象的加锁),使用对象的属性就可以在属性的实现代码中进行控制,而直接锁定对象本身是无法实现对同步访问的控制的。
明白了不少东西,但是想在自己的实际工作中应用起来可能还需要一些时间,毕竟我线在的任务涉及的大量线程的是没有的,即使有,再线程同步的时候也需要加小心,毕竟无意义的加锁是会使系统变得很慢,当变得相当的慢的时候,相信用户体验就和死锁没有什么区别了。