接下来我主要讲一下自己在学习windows核心编程中对于临界区线程同步方式的使用。
临界区线程同步在windows核心编程中被称为关键段线程同步,以下统称关键段关键段是一小段代码,它在执行之前需要独占对一些资源的访问权。缺点:能且只能用在一个进程中的多线程同步。可能陷入死锁,因为我们无法为进入关键段的线程设置最大等待时间。接下来我介绍一些关键段线程同步的使用先看一个事例代码
[html] view plain copyint g_nSum = 0; CRITICAL_SECTION g_cs; DWord WINAPI FirstThread(PVOID pvParam) { EnterCriticalSection(&g_cs); g_nSum = 0; for (int n = 0; n < 10000; ++n) { g_nSum += n; } LeaveCriticalSection(&g_cs); return g_nSum; } DWORD WINAPI SecondThread(PVOID pvParam) { EnterCriticalSection(&g_cs); g_nSum = 0; for (int n = 0; n < 10000; ++n) { g_nSum += n; } LeaveCriticalSection(&g_cs); return g_nSum; } 在使用关键段(CRITICAL_SECTION)时,只有两个必要条件:1、想要访问资源的线程必须知道用来保护资源的CRITICAL_SECTION对象地址。CRITICAL_SECTION对象可以作为全局对象来分配,也可以作为局部对象来分配,或者从堆中动态地分配。2、如何线程在试图访问被保护的资源之前,必须对CRITICAL_SECTION结构的内部成员进行初始化。关键段线程同步常用函数介绍[html] view plain copy//1、首先我们要分配一个CRITICAL_SECTION对象,并进行初始化(使用关键段同步的线程必须调用此函数) void InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection ) //2、当知道线程将不再需要访问共享资源时,我们应该调用下边的函数来清理CRITICAL_SECTION结构 void DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection ) //3、在对保护的资源进行访问之前,必须调用下面的函数 void EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection ) //可以对上边的函数多次调用,表示调用线程被获准访问的次数 //4、也可以用下边的函数代替EnterCriticalSection BOOL TryEnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection ) //通过返回值判断当前线程是否获准访问资源,线程永远不会进入等待状态,如果 //返回TRUE表示该线程获准并正在访问资源,离开时必须调用LeaveCriticalSection() //5、在代码完成对资源的访问后,必须调用以下函数,释放访问权限 void LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection ) //转载请注明文章来自:http://blog.csdn.net/windows_nt 以上访问方式(EnterCriticalSection方式)可能会使调用线程切换到等待状态,这意味着线程必须从用户模式切换到内核模式,这个切换开销非常大。为了提高关键段的性能,Microsoft把旋转锁合并到了关键段中。因此,当调用EnterCriticalSection的时候,它会用一个旋转锁不断地循环,尝试在一段时间内获得对资源的访问权,只有当尝试失败时,线程才会切换到内核模式并进入等待状态。[html] view plain copy//1、为了在使用关键段的时候同时使用旋转锁,我们必须调用下面的函数来初始化关键段 BOOL InitializeCriticalSectionAndSpinCount( LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount ) //第二个参数dwSpinCount表示我们希望旋转锁循环的次数。 //2、我们也可以调用下面的函数来改变关键段的旋转次数 DWORD SetCriticalSectionSpinCount( LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount ) //如果主机只有一个处理器,函数会忽略dwSpinCount参数3、等待一个mutex时,你可以指定“结束等待”的世间长度,但对于critical section则不行。
造成以上差别的主要原因是:mutex是内核对象,critical section非内核对象。以下是mutex和critical section的相关函数比较:
临界区 | 互斥器 |
CRITICAL_SECTIONInitializeCriticalSection() | CreateMutex()OpenMutex() |
EnterCriticalSection() | WaitForSingleObject()WaitForMultipleObjects()MsgWaitForMultipleObjects() |
LeaveCriticalSection() | ReleaseMutex() |
DeleteCriticalSection() | CloseHandle() |
在一个适当的程序中,线程绝对不应该在它即将结束前还拥有一个mutex,因为这意味着线程没有能够适当地清除其资源。不幸的是,我们并不身处一个完美的世界,有时候,因为某种理由,线程可能没有在结束前调用ReleaseMutex()。为了解决这个问题,mutex有一个非常重要的特性。这性质在各种同步机制中是独一无二的,如果线程拥有一个mutex而在结束前没有调用ReleaseMutex(),mutex不会被摧毁,取而代之的是,该mutex会被视为“未被拥有”以及“未被激发”,而下一个等待中的线程会被以WAIT_ABANDONED_0通知。无论线程是因为ExitThread()而结束,或是因当掉而结束,这种情况都存在。
任何时候只要你想锁住超过一个以上的同步对象,你就有死锁的潜在病因。如果总是在相同时间把所有对象都锁住,问题可去矣。事例如下,存在潜在死锁的可能:
[html] view plain copyvoid SwapLists(List* list1, List* list2) { EnterCriticalSection(list1->critical_sec); EnterCriticalSection(list2->critical_sec); //list1,list2数据交换 LeaveCriticalSection(list1->critical_sec); LeaveCriticalSection(list2->critical_sec); } 正确的做法:[html] view plain copyvoid SwapLists(List* list1, List* list2) { HANDLE arrHandles[2]; arrHandles[0] = list1->hMutex; arrHandles[1] = list2->hMutex; WaitForMultipleObjects(2, arrHandles, TRUE, INFINITE); //list1,list2数据交换 ReleaseMutex(arrHandles[0]); ReleaseMutex(arrHandles[1]); } 三、前边讲过了互斥器线程同步-----windows核心编程-互斥器(Mutexes),这章我来介绍一下信号量(semaphore)线程同步。
理论上说,mutex是semaphore的一种退化。如果你产生一个semaphore并令最大值为1,那就是一个mutex。也因此,mutex又常被称为binary semaphore。如果某个线程拥有一个binary semaphore,那么就没有其他线程能够获得其拥有权。但是在win32中,这两种东西的拥有权的意义完全不同,所以它们不能够交换使用,semaphore不像mutex,它并没有所谓的“wait abandoned”状态可以被其他线程侦测到。每当一个锁定动作成功,semaphore的现值就会减1,你可以使用任何一种wait...()函数来要求锁定一个semaphore。如果semaphore的现值不为0,wait...()函数会立刻返回,这和mutex很像,当没有任何线程拥有mutex,wait...()函数会立刻返回。注意,如果锁定成功,你也不会收到semaphore的拥有权。因为可以有一个以上的线程同时锁定一个semaphore。所以谈semaphore的拥有权并没有太多实际意义。在semaphore身上并没有所谓“独占锁定”这种事情。也因为没有所有权的观念,一个线程可以反复调用wait...()函数以产生新的锁定。这和mutex绝不相同:拥有mutex的线程不论再调用多少次wait...()函数,也不会被阻塞住。与mutex不同的是,调用ReleaseSemaphore()的那个线程,并不一定就得是调用wait...()的那个线程。任何线程都可以在任何时间调用ReleaseSemaphore(),解除被任何线程锁定的semaphore。
以下是我对三种同步方式中,常用到的函数的总结。
临界区 | 互斥器 | 信号量 |
CRITICAL_SECTIONInitializeCriticalSection() | CreateMutex()OpenMutex() | CreateSemaphore |
EnterCriticalSection() | WaitForSingleObject()WaitForMultipleObjects()MsgWaitForMultipleObjects() | WaitForSingleObject()WaitForMultipleObjects()MsgWaitForMultipleObjects()... |
LeaveCriticalSection() | ReleaseMutex() | ReleaseSemaphore() |
DeleteCriticalSection() | CloseHandle() | CloseHandle() |
事件线程同步----- window核心编程-内核对象线程同步
四、
上一章讲了关键字(临界区)线程同步,使用关键字线程同步,我们很容易陷入死锁的情形,这是因为我们无法为进入关键段指定一个最长等待时间。
本章将讨论如何使用内核对象来对线程同步。我们也将看到,与用户模式下的同步机制(关键段同步)相比,内核对象的用途要广泛的多。实际上,内核对象唯一的缺点就是它们的性能。内核对象包括进程、线程以及作业,几乎所有这些内核对象都可以用来进行同步。对线程同步来书,这些内核对象中的每一种要么处于触发状态,要么处于未触发状态。Microsoft为每种对象创建了一些规则,规定如何在这两种状态之间进行转换。例如,进程内核对象在创建的时候总是处于未触发状态。当进程终止的时候,操作系统会自动使进程内核对象变成触发状态。当进程内核对象被触发后,它将永远保持这种状态,再也不会变回到未触发状态。在进程内核对象的内部有一个布尔变量,当系统创建内核对象的时候会把这个变量的值初始化为false(未触发)。当进程终止的时候,操作系统会自动把相应的内核对象中的这个布尔值设为true,表示该对象已经被触发。
下边讲一些内核同步中用到的函数。等该函数使一个线程自愿进入等待状态,直到指定的内核对象被触发为止。DWORD waitForSingleObject(HANDLE hObject, DWORD dwMilliseconds);//hObject:内核对象句柄//dwMilliseconds等待时间ms为单位,INFINITE为一直等待,只到内核对象被触发为止。DWORD WaitForMultipleObjects( DWORD nCount, CONST HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds );//waitForSingleObject和WaitForMultipleObjects相似,唯一的不同之处在于它允许调用线程同时检查多个内核对象的触发状态/*函数的返回值告诉调用方函数为什么它得以继续运行。如果给bWaitAll传的是FALSE,那么只要任何一个对象被触发,函数就会立即返回。这时的返回值是WAIT_OBJECT_0和(WAIT_OBJECT_0+dwCount-1)之间的任何一个值。换句话说,如果返回值既不是WAIT_TIMEOUT,也不是WAIT_FAILED,那么我们应该把返回值减去WAIT_OBJECT_0。得到的数值是我们在第二个参数中传的句柄数组的一个索引,用来告诉我们被触发的是那个对象。*/
//下面的事例代码可以更清晰的对此进行解释[html] view plain copyHANDLE h[3];//我的博客:<a href="http://blog.csdn.net/windows_nt">http://blog.csdn.net/windows_nt</a> h[0] = hPRocess1; h[1] = hProcess2; h[2] = hProcess3; DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000); switch(dw) { case WAIT_FAILED: //Bad call to function (invalid handle) break; case WAIT_TIMEOUT: //None of the objects became signaled within 5000 milliseconds break; case WAIT_OBJECT_0 + 0: //h[0] signaled, hProcess1 terminated break; case WAIT_OBJECT_0 + 1: //h[1] signaled, hProcess2 terminated break; case WAIT_OBJECT_0 + 2: //h[2] signaled, hProcess3 terminated break; }LPCSTR lpName );//事件内核对象的名字,可以为空
//新版本
HANDLE CreateEventEx(LPSECURITY_ATTRIBUTES psa, //安全属性PCTSTR pszName, //名字DWORD dwFlags, //是否触发DWORD dwDesiredaccess)
//dwDesiredAccess:允许我们指定在创建事件时返回的句柄对事件有何种访问权限。这是一种创建事件句柄的新方法,它可以减少权限,相比较而言,CreateEvent()总是被授予全部权限。但CreateEventEx()更有用的地方在于它允许我们以减少权限的方式来打开一个已经存在的事件,而CreateEvent()总是要求全部权限。
//下边这个例子展示了如何使用事件内核对象来对线程进行同步。[html] view plain copy//Create a global handle to a manual-reset, nonsignaled event. HANDLE g_hEvent; int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd ) { //Create the manual-reset, nonsignaled event g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //Spawn 3 new threads HANDLE hThread[3]; DWORD dwThread; hThread[0] = _beginthread(NULL, 0, wordCount, NULL, 0, &dwThread); hThread[1] = _beginthread(NULL, 0, SpellCheck, NULL, 0, &dwThread); hThread[2] = _beginthread(NULL, 0, GrammarCheck, NULL, 0, &dwThread); OpenFileAndReadContentsIntoMemory(...); //allow all 3 threads to access the memory SetEvent(g_hEvent); } DWORD WINAPI wordCount(PVOID pvParam) { //Wait until the file's data is in memory waitForSingleObject(g_hEvent, INFINITE); //access the memory block. return 0; } DWORD WINAPI SpellCheck(PVOID pvParam) { //Wait until the file's data is in memory waitForSingleObject(g_hEvent, INFINITE); //access the memory block. return 0; } DWORD WINAPI GrammarCheck(PVOID pvParam) { //Wait until the file's data is in memory waitForSingleObject(g_hEvent, INFINITE); //access the memory block. return 0; }下边是我自己写的一个事例片段,很简单
[html] view plain copyCString strName = _T(""); UINT CBcgTestDlg::ThreadWorkFunc(LPVOID lPvoid) { for (int n = 0; n < 10000; n++) { strName = _T("http://blog.csdn.net/windows_nt"); strName = strName + _T("/n"); TRACE(strName); } return 0; } void CBcgTestDlg::OnOK() { CWinThread* pThread = NULL; for (int n = 0; n < 10000; ++n) { if (pThread) { WaitForSingleObject(pThread->m_hThread, INFINITE); delete pThread; } pThread = AfxBeginThread(ThreadWorkFunc, NULL, 0, CREATE_SUSPENDED, NULL); if (pThread) { pThread->m_bAutoDelete = FALSE; pThread->ResumeThread(); } } } 注意线程函数可以为类函数,但必须是静态函数新闻热点
疑难解答