前面C++ 关键段(Critical Section)CS深入浅出 之多线程(六)给出了CS的CRITICAL_SECTION和临界区本质理论!(如果你不是很清楚建议先看一下,那么下面你才会更加容易理解)
#include <iostream> #include <process.h> #include <windows.h> using namespace std; long g_nNum; unsigned int __stdcall Fun(PVOID pPM); const int THREAD_NUM = 10; //关键段变量声明 CRITICAL_SECTION g_csThreadParameter, g_csThreadCode; int main() { cout<<"经典线程同步关键段"<<endl; //关键段初始化 InitializeCriticalSection(&g_csThreadParameter); InitializeCriticalSection(&g_csThreadCode); HANDLE handle[THREAD_NUM]; g_nNum = 0; int i = 0; while (i < THREAD_NUM) { EnterCriticalSection(&g_csThreadParameter);//进入子线程序号关键区域 handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL); ++i; } WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE); DeleteCriticalSection(&g_csThreadCode); DeleteCriticalSection(&g_csThreadParameter); system("pause"); return 0; } unsigned int __stdcall Fun(PVOID pPM) { int nThreadNum = *(int *)pPM; LeaveCriticalSection(&g_csThreadParameter);//离开子线程序号关键区域 Sleep(50);//some work should to do EnterCriticalSection(&g_csThreadCode);//进入各子线程互斥区域 g_nNum++; Sleep(0);//some work should to do cout<<"线程编号为:"<<nThreadNum<<"全局资源值为:"<<g_nNum<<endl; LeaveCriticalSection(&g_csThreadCode);//离开各子线程互斥区域 return 0; }
运行效果如下:
OK 断点如下
由断点可知函数应该26--->27--->38这样依次调用,可是并未这样,而是多次进入:
EnterCriticalSection(&g_csThreadParameter);//进入子线程序号关键区域
这一语句。这说明主线程能多次进入这个关键区域!找到主线程和子线程没能同步的原因后,下面就来分析下原因的原因吧!
那么就要回到我前面的C++ 关键段(Critical Section)CS深入浅出 之多线程(六)那么我再次把这些添加到这里来分析!
关键段是有“线程所有权”概念的,仔细看看你就会很明白的哈!
下面引用morewindorws一些重要信息!
旋转锁的设置,单CPU下忽略
由这个结构可以知道关键段会记录拥有该关键段的线程句柄即关键段是有“线程所有权”概念的。事实上它会用第四个参数OwningThread来记录获准进入关键区域的线程句柄,如果这个线程再次进入,EnterCriticalSection()会更新第三个参数RecursionCount以记录该线程进入的次数并立即返回让该线程进入。其它线程调用EnterCriticalSection()则会被切换到等待状态,一旦拥有线程所有权的线程调用LeaveCriticalSection()使其进入的次数为0时,系统会自动更新关键段并将等待中的线程换回可调度状态。
因此可以将关键段比作旅馆的房卡,调用EnterCriticalSection()即申请房卡,得到房卡后自己当然是可以多次进出房间的,在你调用LeaveCriticalSection()交出房卡之前,别人自然是无法进入该房间。
回到这个经典线程同步问题上,主线程正是由于拥有“线程所有权”即房卡,所以它可以重复进入关键代码区域从而导致子线程在接收参数之前主线程就已经修改了这个参数。所以关键段可以用于线程间的互斥,但不可以用于同步。
另外,由于将线程切换到等待状态的开销较大,因此为了提高关键段的性能,Microsoft将旋转锁合并到关键段中,这样EnterCriticalSection()会先用一个旋转锁不断循环,尝试一段时间才会将线程切换到等待状态。下面是配合了旋转锁的关键段初始化函数
函数功能:初始化关键段并设置旋转次数
函数原型:
BOOLInitializeCriticalSectionAndSpinCount(
LPCRITICAL_SECTIONlpCriticalSection,
DWORDdwSpinCount);
函数说明:旋转次数一般设置为4000。
函数功能:修改关键段的旋转次数
函数原型:
DWORDSetCriticalSectionSpinCount(
LPCRITICAL_SECTIONlpCriticalSection,
DWORDdwSpinCount);
《Windows核心编程》第五版的第八章推荐在使用关键段的时候同时使用旋转锁,这样有助于提高性能。值得注意的是如果主机只有一个处理器,那么设置旋转锁是无效的。无法进入关键区域的线程总会被系统将其切换到等待状态。
最后总结下关键段:
1.关键段共初始化化、销毁、进入和离开关键区域四个函数。
2.关键段可以解决线程的互斥问题,但因为具有“线程所有权”,所以无法解决同步问题。
3.推荐关键段与旋转锁配合使用。
下一篇《C++ 经典线程同步 事件Event第九篇》将介绍使用事件Event来解决这个经典线程同步问题。
期待将持续更新! syw_selfimpr新浪微博地址: http://weibo.com/u/2945271402
注:以上在VS2010 正常编译!