为什么.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);
整个代码位于mscorlib Version=4.0.0.0的System.Collections.Hashtable的public virtual object this[object key]中。
问题是:
为什么在那里使用Thread.Sleep(1)

7
似乎是在上下文切换之前进行自旋等待。也就是说,在将线程置于睡眠状态之前,它会检查条件8次。 - vgru
4
关键是允许操作系统调度其他任务。这个线程暂停的作用类似。 - Konstantin
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变量是一个易失的布尔值。作者在英语上遇到了一些困难,"violate read"应该是"volatile read"。基本思想是尽量避免让步,线程上下文切换非常昂贵,希望写入者能够快速完成。如果这不起作用,则明确地让步以避免烧毁CPU。今天可能会使用Spinlock来编写此代码,但Hashtable非常古老。关于内存模型的假设也是如此。


8
如果你只是想让出让出CPU的话,我认为建议使用Sleep(0)。而如果你真正需要让线程休眠的话,使用Sleep(1)。请注意,这两种方法功能不同。 - Ben Voigt
11
Sleep(0)只有在有其他具有更高优先级的线程准备好运行时才会让出CPU时间。但这不是本意。 - Hans Passant
只有相同优先级的线程才会实际交替执行......这通常是yield所表示的意思。 - Ben Voigt
4
相等或更高。写操作需要很少的时间,隐含的意思是如果在8次尝试后这仍然不起作用,则写线程必须已被挂起。这不是很好的代码。 - Hans Passant
如果有一个优先级更高的线程准备好运行,它已经在运行了。如果您的代码正在运行,则所有更高优先级的线程都已经在运行(在其他核心上)或被阻塞。因此,Sleep(0)仅让出相同优先级的线程。当然要考虑动态优先级。 - Ben Voigt

7

没有其他实现代码的访问权限,我只能根据您发布的内容做出有根据的猜测。

尽管如此,它似乎正在尝试更新Hashtable中的某些内容,无论是在内存中还是在磁盘上,并在等待其完成时进行无限循环(通过检查isWriterInProgress可见)。

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


5
我没有阅读源代码,但看起来这是一种无锁并发技术。您正在尝试从哈希表中读取数据,但其他人可能正在写入,所以您需要等待直到isWriterInProgress未设置并且您已读取的版本未更改。
这并没有解释为什么我们总是至少等待一次,编辑:因为我们不会,感谢@Maciej指出。当没有争用时,我们立即进行处理。我不知道为什么8是魔法数字而不是4或16。

3
不,我们不需要。在执行“++num3”之后,“num3”的值将变为1。或者我严重缺乏咖啡因。 - Maciej Stachowski
只有在连续8次迭代中写入程序正在进行或版本错误时,才会执行休眠。 可能是为了给写入线程完成工作的机会。 - dan04
3
如果我们在每次调用[]运算符时都调用Sleep(1),那么性能会非常糟糕。请记住,Thread.Sleep(1)实际上并不能让你睡眠1毫秒——它更像是15毫秒。 - Maciej Stachowski
那么num4与所有这些有什么关系? - BlackBear
2
@BlackBear - 起初我也有点困惑,但当你意识到 version 可能指的是哈希表中数据的版本(即每次写入都会创建一个新版本)时,它就有意义了 :) - Maciej Stachowski
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接