自旋锁在前面的博文中已经介绍过(Interlocked函数族),用自旋锁的缺点就是不停的询问CPU某个事件是不是发生了,某个变量是不是更改了值什么的,浪费CPU时间。因此推荐短期能够访问到的资源使用自旋锁,不然就用内核对象进行互斥或等待。
除此之外,读书过程中那两个作者基佬还提到了说,如果访问同一自旋锁的线程优先级各不相同的话,那么自旋锁就会更降低效率。
如下代码:
这是主线程中的等待代码
HANDLE hThread = (HANDLE)_beginthreadex(NULL,NULL,Thread_Low,NULL,CREATE_SUSPENDED,NULL);
SetThreadPriority(hThread,THREAD_PRIORITY_IDLE);
SetThreadAffinityMask(hThread,0x00000001);
SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_HIGHEST);
SetThreadAffinityMask(GetCurrentThread(),0x00000001);
ResumeThread(hThread);
CloseHandle(hThread);
while(InterlockedExchange(&g_x,FALSE) == FALSE);
这是线程函数
unsigned int _stdcall Thread_Low(void* pParam)
{
InterlockedExchange(&g_x,TRUE);
return 0;
}
从线程优先级和调度的概念上我们可以知道,由于主线程一直都处于可调度状态,而且优先级高,它就一直都在占用CPU,第二线程只有快被饿死的时候才会被OS动态提升线程优先级从而得以运行。于是,对全局变量g_x的改变被延后了数秒才得以进行。从而,主线程需要延迟数秒才能从spinlock中退出。
以下是完整代码,将两个线程的CPU都设置成只允许在1号CPU上运行是为了模拟在单核状态下CPU时间如何被高优先级的线程一直侵占。
volatile LONG g_x = FALSE;
unsigned int _stdcall Thread_Low(void* pParam)
{
InterlockedExchange(&g_x,TRUE);
return 0;
}
void main()
{
HANDLE hThread = (HANDLE)_beginthreadex(NULL,NULL,Thread_Low,NULL,CREATE_SUSPENDED,NULL);
_ASSERT(SetThreadPriority(hThread,THREAD_PRIORITY_IDLE));
SetThreadAffinityMask(hThread,0x00000001);
_ASSERT(SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_HIGHEST));
SetThreadAffinityMask(GetCurrentThread(),0x00000001);
ResumeThread(hThread);
CloseHandle(hThread);
LARGE_INTEGER liFreq = {0};
LARGE_INTEGER liStart = {0};
LARGE_INTEGER liEnd = {0};
QueryPerformanceFrequency(&liFreq);
QueryPerformanceCounter(&liStart);
while(InterlockedExchange(&g_x,FALSE) == FALSE);
QueryPerformanceCounter(&liEnd);
cout<<(liEnd.QuadPart - liStart.QuadPart) / (double)liFreq.QuadPart<<'s'<<endl;
return;
}
如果删除对CPU亲缘性的要求,那么执行效率会快很多,这是因为低级线程被分配到其它CPU上去了。
总结:如果你用自旋锁,那么记得在等待时调用SwitchToThread以使得低等级的线程可以运行。否则你将浪费大量的CPU时间并将导致低等级的线程不必要的长延迟。
实践:在main的while循环中加上SwitchToThread看看和不加,延迟时间各是多少。
另外我有一个问题,Sleep(0)按照书中的说法,不是只调度同级或者高级线程么,为什么我用Sleep(0)来放弃时间片得到的效果和SwitchToThread差不多呢?按照我的理解来说,Sleep(0)应该改善不了多少状况啊,求大大解答。
自问自答一下:
Sleep(0)除了XP和2000调用同级或者高级线程外,其余都是调用所有线程