代码改变世界

揭示同步块索引(下):总结

2009-08-18 15:57  横刀天笑  阅读(3840)  评论(7编辑  收藏  举报

前面,我用两篇文章详细的讨论了同步块索引在lock和GetHashCode所起的作用。不过两篇文章是分开来讨论的。那可能有人会问,如果我有一个object,它既作为lock的lockHelper对象,也要调用它的GetHashCode方法该怎么办,难道这个同步块索引还可以承担这两个任务么。同步块索引是可以承担这两个任务,但是里面却隐藏着更大的秘密,我们先来看看与同步块索引相关的结构:

大致就是这样的一个结构,一个对象的ObjectHeader中的SyncBlockIndex指向一个Sync Block Entry Table中的一项,这里用虚线表示,是说明这里不是使用指针直接的指向,而是一个索引,这样有个什么好处呢,就是CLR可以随便把这个Table放在哪里,也可以按需增大这个Table的容量,反正我这里使用的是索引而不是指针,是间接的指向。这个Table里的每一项都是一个SyncTableEntry,这个SyncTableEntry有两个字段,第一个字段是一个SyncBlock的指针,指向一个SyncBlock对象。还有一个字段是一个Object指针,有了这个指针CLR就可以跟踪这个SyncBlock是哪个对象的,而且SyncTableEntry和SyncBlock不是放在GC管理的内存中,所以可以根据这个Object*来跟踪对应的对象的实例,当对象死亡后,可以回收对应的SyncBlock和SyncTableEntry,不过这个Object的指针是一个弱引用(弱引用的作用是,如果没有任何强引用引用该对象,则该对象可以被认为是垃圾,允许被垃圾收集)。

不过要注意的是,上面这种结构并不是一个对象“与生俱来”的,也就是说对象刚初始化的时候并不如此。当一个对象刚初始化的时候,在ObjectHeader中,SyncBlockIndex字段是为0的,这个在上一篇文章中的Visual Studio + SOS的实验中我们已经见到过。而如果调用对象的GetHashCode方法,则对象的ObjectHeader中的SyncBlockIndex字段的低26位则用来存储该对象的HashCode,而高6位作为一个标识,表示现在SyncBlockIndex作为存储HashCode之用,具体做法就是将这个SyncBlockIndex与BIT_SBLK_IS_HASHCODE (#define BIT_SBLK_IS_HASHCODE 0x04000000)作或运算,判别的时候作一下与运算。这个在上一篇文章中也介绍了。而如果调用对象的GetHashCode方法之后,继续将该对象作为lock的对象使用呢?这个时候SyncBlockIndex的低26位会摇身一变,变成一个索引,而且还与BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX (#define BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX 0x08000000)作一下或运算,表示这个SyncBlockIndex现在啊既有存储HashCode之功用,又要作为lock的对象。

那既然这低26位变成了索引,那原来的HashCode跑到哪里去了呢?这个就要一探SyncBlock的结构了:

SyncBlock结构

我们看到最后一个字段,这个字段就是如果SyncBlockIndex还做其他用途是,CLR会将计算所得的hashcode放到这里。而如果对象只作lock对象使用,而没有调用GetHashCode方法,则这个字段为0。根据调用的顺序,这个m_dwHashCode的设置有两种方式:

1、已经调用了GetHashCode方法,然后作lock之用,那这里的m_dwHashCode就是之前存储在SyncBlockIndex中的低26位。

2、先作lock之用,然后调用GetHashCode,那m_dwHashCode就是当时新生成的HashCode,然后放在这里的。

从图中我们还有ADIndex这么一个字段,这个字段是表示当前这个对象属于哪个AppDomain,实际上这个字段也可以在SyncBlockIndex里设置,但是如果SyncBlockIndex要担负别的责任,比如该对象作为lock对象时,ADIndex就在SyncBlock里这个字段设置了。关于为什么需要这个ADIndex我现在还没弄清楚,等我弄明白了,再来更新这篇文章。

SyncBlock的第一个字段是AwareLock,实际上这个东西和我第一篇文章中提到的CRITICAL_SECTION结构是一样的,具体细节可以参见“揭示同步索引块(上)-从lock开始”这篇文章。

而这里的SLink字段有两个作用:

1、当SyncBlock是活动的时候,这个字段将作为一个队列,保存在这里排队的线程(作为lock对象时)。

2、当SyncBlock被回收时,这个字段就作为空闲的SyncBlock列表。

 

好了,同步块索引的相关用途和结构在、下这三篇文章中基本讨论完了。没想到这么一个小小的,不起眼的同步块索引却有这么一番作用。从这个同步块索引的使用上,也可以看得出来微软的CLR Team在设计的时候,对内存、性能可谓斤斤计较,充分的利用每一个bit,一个bit的不同就会表示不同的作用。