为什么.NET内部Hashtable中有Thread.Sleep(1)?


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.Hashtable(mscorlib程序版本= 4.0.0.0)。

问题是:

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


7
看起来像上下文切换之前的自旋等待。即,在线程进入睡眠状态之前,它会检查8次状况。
Groo

4
相似的线程; Crux允许OS安排其他任务
Konstantin

Answers:


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变量是易失性布尔。作者在英语上遇到了一些麻烦,“违禁阅读”是“易失性阅读”。基本思想是尽量避免屈服,线程上下文切换非常昂贵,并且希望编写者能尽快完成。如果没有成功,则显式屈服以避免燃烧cpu。这可能是今天用Spinlock编写的,但是Hashtable很老了。关于内存模型的假设也是如此。


8
我以为Sleep(0)建议,如果您只想要一个产量。 Sleep(1)实际上是睡觉。
Ben Voigt

11
只有在有另一个线程可以更高优先级运行时,Sleep(0)才会产生。这里不是故意的。
汉斯·帕桑

实际上只对优先级相同的线程...这通常是yield的意思。
Ben Voigt

4
等于或更高。写入花费的时间很少,这隐含的是,如果经过8次尝试后仍无法正常工作,则必须暂停写入线程。这不是很棒的代码。
汉斯·帕桑

如果有一个更高优先级的线程准备运行,那么它将已经在运行。如果您的代码正在运行,则所有优先级更高的线程也正在运行(在其他内核上)或被阻塞。因此,睡眠(0)仅产生相等的优先级。当然考虑动态优先级。
Ben Voigt

7

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

也就是说,它似乎正在尝试更新Hashtable中的某些内容(无论是内存还是磁盘),并在等待其完成之前进行无限循环(如通过检查所示isWriterInProgress)。

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


5

我还没有阅读源代码,但看起来像是无锁的并发性事情。您正在尝试从哈希表中读取数据,但是可能有人正在向其写入数据,因此您要等到isWriterInProgress未设置哈希值并且所读取的版本没有更改。

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


3
不,我们不。之后++num3num3将为1。或者我严重缺乏咖啡因。
Maciej Stachowski13年

仅在连续8个迭代(写入程序正在进行或版本错误)之后才进行睡眠。大概是给作者线程一个完成其工作的机会。
dan04

3
如果在每次对[]运算符的调用中都调用Sleep(1),那将对性能造成严重影响。记住了Thread.Sleep(1)实际上并没有让你睡1毫秒-它更像15
马切伊Stachowski

num4与这一切有什么关系?
BlackBear

2
@BlackBear-它也一开始也让我失望,但是当您意识到version可能意味着哈希表中数据的版本(即每次写入都会创建一个新的)时,这确实很有意义:)
Maciej Stachowski13年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.