2014-05-07 28 views
0

我对使用EnterCriticalSection和LeaveCriticalSection方法的Windows上的关键部分的公平性有疑问。 MSDN文档规定:“不能保证线程获得关键部分所有权的顺序,但是,系统对所有线程都是公平的。” 问题出现在我写的应用程序中,即使很长时间后,它也会阻止某些永不进入临界区的线程;所以我用一个简单的c程序进行了一些测试,以验证这种行为,但是当有许多线程在内部等待时,我注意到了奇怪的结果。 这是测试程序的代码:Windows关键部分公平性

CRITICAL_SECTION CriticalSection; 

DWORD WINAPI ThreadFunc(void* data) { 
    int me; 
    int i,c = 0;; 
    me = *(int *) data; 
    printf(" %d started\n",me); 
    for (i=0; i < 10000; i++) { 
    EnterCriticalSection(&CriticalSection); 
    printf(" %d Trying to connect (%d)\n",me,c); 
    if(i!=3 && i!=4 && i!=5) 
     Sleep(500); 
    else 
     Sleep(10); 
    LeaveCriticalSection(&CriticalSection); 
    c++; 
    Sleep(500); 
    } 
    return 0; 
} 

int main() { 
    int i; 
    int a[20]; 
    HANDLE thread[20]; 

    InitializeCriticalSection(&CriticalSection); 
    for (i=0; i<20; i++) { 
     a[i] = i; 
     thread[i] = CreateThread(NULL, 0, ThreadFunc, (LPVOID) &a[i], 0, NULL); 
    } 
} 

这样做的结果是,一些线程被阻塞了很多很多次,和其他一些人经常进入临界区。我也注意到,如果你改变更快的睡眠(10毫秒),一切都可能恢复到公平,但我没有发现睡眠时间和公平之间有任何联系。 但是,这个测试例子比我真正的应用程序代码更好,它更加复杂,并且显示某些线程实际上处于饥饿状态。为了确定饥饿的线程是活着的并且工作正常,我做了一个测试(在我的应用程序中),其中在关键部分输入5次后我终止了线程:结果是,每个线程最后都进入了,所以我确定它们都是活着的并且被互斥体阻塞。 我是否必须假定Windows对线程真的不公平? 你知道这个问题的解决方案吗?

编辑:与pthreads在linux中相同的代码,按预期工作(没有线程饿死)。

编辑2:我发现了一个工作解决方案,迫使公平,使用CONDITION_VARIABLE。 可以从这篇文章(link)推断出所需的修改。

+2

MSDN文章没有提到公平性。从Vista和Server 2003 SP1开始没有任何问题。公平会导致锁车队,背景[在这里](http://joeduffyblog.com/2006/12/14/anticonvoy-locks-in-windows-server-2003-sp1-and-windows-vista/)。 –

+0

信号量,临界区和互斥量并不总是同步的最佳选择。对于高度争议的资源,您必须非常小心地使用您的资源,有时让单个线程管理该资源是可取的,因为您不需要阻止该资源上的任何其他线程。一个典型的例子就是UI。 – Mgetz

+0

@HansPassant:我读过这篇文章,谢谢,所以我必须假定不使用CriticalSection数据结构,因为它不公平。 我引用了这个链接:[链接](http://msdn.microsoft.com/en-us/library/windows/desktop/ms683472%28v=vs.85%29.aspx),它确实说我写了什么。 – Mat

回答

0

因为关键部分持续了很长时间,所以无论如何你都会遇到饥饿问题。
我认为MSDN可能暗示调度器对于唤醒线程是公平的,但由于没有锁定采集命令,因此它可能并不像您所期望的那样“公平”。 您是否尝试过使用互斥锁代替临界区?另外,你有没有尝试调整旋转计数?

如果您可以避免长时间锁定关键部分,那么这可能是解决这个问题的更好方法。

例如,您可以重构代码以使单个线程处理您的长时间运行的操作,而其他线程将请求排队到该线程,从而阻止完成事件。管理队列时,只需短时间锁定关键部分。当然,如果这些操作必须与其他操作相互排斥,那么您需要小心。如果所有这些东西都不能同时运行,那么你也可以通过队列将其序列化。

另外,也许看看使用升压asio。您可以使用线程池和链来防止多个异步处理程序同时运行,否则同步会成为问题。

+0

我没有'尝试一个互斥体,因为我认为它处理的公平性比关键部分(但也许我错了)。 我知道我可以通过重构我的代码来使用一些解决方法,我只是想了解是否有可能避免它。 我会尝试使用互斥锁(旋转计数不参与此操作,但无法达到预期效果)。 – Mat

+0

不幸的是,与互斥量相同的行为。 – Mat

0

我想你应该检讨的几件事情:

  • 于9997 10000情况下,你跳转到Sleep(500)。几乎每次成功尝试获取关键部分时,每个线程都会将citical部分保存500毫秒。

  • 线程在释放关键部分后执行另一个Sleep(500)。因此,通过握住关键部分,单线程占用将近50%(49.985%)的时间 - 无论如何!

  • 幕后:Joe Duffy互斥锁的等待列表以FIFO顺序保存,操作系统始终将线程唤醒至等待队列的前端。

假设你这样做的目的是显示的行为:启动这些线程的20可能导致10秒的最后一个线程的最小等待时间得到一个单一的逻辑时,处理器进入临界区处理器完全可用于此测试。

多长时间不同测试/什么CPU?什么Windows版本?你应该能够写下更多的事实:线程活动与线程ID的直方图可以说明公平性。

必须在短时间内购买关键部分。在大多数情况下,共享资源可以更快处理。临界区域内的Sleep几乎肯定指向设计缺陷。

提示:减少在关键部分花费的时间或调查Semaphore Objects