70

最近我在阅读 .NET Hashtable的实现,遇到了一段我不明白的代码。部分代码是:

int num3 = 0;
int num4;
do
{
   num4 = this.version;
   bucket = bucketArray[index];
   if (++num3 % 8 == 0)
     Thread.Sleep(1);
}
while (this.isWriterInProgress || num4 != this.version);

整个代码在public virtual object this[object key]( System.Collections.Hashtablemscorlib Version=4.0.0.0) 之内。

问题是:

在那里的原因是什么Thread.Sleep(1)

4

3 回答 3

70

Sleep(1) 是 Windows 中记录的让处理器让出并允许其他线程运行的方法。您可以在带有注释的参考源中找到此代码:

   // Our memory model guarantee if we pick up the change in bucket from another processor,
   // we will see the 'isWriterProgress' flag to be true or 'version' is changed in the reader.
   //
   int spinCount = 0;
   do {
       // this is violate read, following memory accesses can not be moved ahead of it.
       currentversion = version;
       b = lbuckets[bucketNumber];

       // The contention between reader and writer shouldn't happen frequently.
       // But just in case this will burn CPU, yield the control of CPU if we spinned a few times.
       // 8 is just a random number I pick.
       if( (++spinCount) % 8 == 0 ) {
           Thread.Sleep(1);   // 1 means we are yeilding control to all threads, including low-priority ones.
       }
   } while ( isWriterInProgress || (currentversion != version) );

isWriterInProgress 变量是一个 volatile bool。作者在英文“violate read”是“volatile read”时遇到了一些麻烦。基本思想是尽量避免让步,线程上下文切换非常昂贵,希望作者能快速完成。如果那没有成功,那么显式让步以避免烧毁 CPU。这可能是今天用 Spinlock 编写的,但 Hashtable 已经很老了。关于内存模型的假设也是如此。

于 2013-11-15T17:29:58.640 回答
7

由于无法访问其余的实现代码,我只能根据您发布的内容做出有根据的猜测。

也就是说,它看起来像是在尝试更新 Hashtable 中的某些内容,无论是在内存中还是在磁盘上,并在等待它完成时执行无限循环(如检查 所示isWriterInProgress)。

如果是单核处理器,则一次只能运行一个线程。像这样进入一个连续循环很容易意味着另一个线程没有机会运行,但是这Thread.Sleep(1)让处理器有机会给作者时间。没有等待,写线程可能永远不会有机会运行,也永远不会完成。

于 2013-11-15T17:10:12.773 回答
5

我还没有阅读源代码,但它看起来像一个无锁并发的东西。您正在尝试从哈希表中读取,但其他人可能正在写入它,因此您等待直到isWriterInProgress未设置并且您已阅读的版本没有更改。

这并不能解释为什么我们总是至少等待一次。编辑:那是因为我们没有,感谢@Maciej 指出这一点。当没有争用时,我们立即进行。不过,我不知道为什么 8 是幻数而不是 4 或 16。

于 2013-11-15T17:04:55.413 回答