本篇文章说明了一种多线程编程中常见的模式,该模式主要描述如下: 1.有一所幼儿园,有若干个老师和很多的孩子,有一个迷宫给孩子们玩 2.老师可以布置迷宫。 3.当某个老师在布置迷宫的时候,为了安全,孩子们不可以在迷宫里 4.不能让一个以上的老师同时布置迷宫,免得把迷宫弄乱 5.在没有老师布置迷宫的时候,孩子们可以自由进出迷宫,在里面玩 6.当某个老师想进入迷宫的时候,他必须挂一块牌子,表示老师要清理迷宫,不让孩子们再进来(但是已经在迷宫里的孩子可以继续玩),当迷宫里已经没有孩子后,老师就可以整理迷宫了,整理完后,老师就可以把牌子摘掉
或者可以这样说:
1.有一个共享的资源 2.一个或者多个线程写该资源(最常见的是一个写线程,如果想改为多个线程写很简单,大多数时候只是加一个关键代码段) 3.在读线程和写线程之间保持互斥 4.写线程之间要保证互斥 5.因为效率的原因,读线程之间不需要保持互斥. 6.因为常常有很多的读线程,写线程必须采取一定的措施,防止自己得不到机会调度
-----------------------------------------------------------------
MSDN上有一个例子,,在这个程序里,是一个写入线程,多个读线程,每个线程都有一个event对象,使用了WaitForMultipleObjects保持互斥
1.这是创建线程的代码段: #define NUMTHREADS 4
HANDLE hGlobalWriteEvent;
void CreateEventsAndThreads(void) { HANDLE hReadEvents[NUMTHREADS], hThread; DWORD i, IDThread;
// Create a manual-reset event object. The master thread sets // this to nonsignaled when it writes to the shared buffer. //写入资源时防止其他线程读取的event
hGlobalWriteEvent = CreateEvent( NULL, // no security attributes TRUE, // manual-reset event TRUE, // initial state is signaled "WriteEvent" // object name );
if (hGlobalWriteEvent == NULL) { // error exit }
// Create multiple threads and an auto-reset event object // for each thread. Each thread sets its event object to // signaled when it is not reading from the shared buffer.
for(i = 1; i <= NUMTHREADS; i++) { // Create the auto-reset event. hReadEvents[i] = CreateEvent( NULL, // no security attributes FALSE, // auto-reset event TRUE, // initial state is signaled NULL); // object not named
if (hReadEvents[i] == NULL) { // Error exit. }
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) ThreadFunction, &hReadEvents[i], // pass event handle 0, &IDThread); if (hThread == NULL) { // Error exit. } } }
2.这是写入资源的代码段,从上下文来看应该是工作在主线程: VOID WriteToBuffer(VOID) { DWORD dwWaitResult, i;
// Reset hGlobalWriteEvent to nonsignaled, to block readers. //阻塞读取线程 if (! ResetEvent(hGlobalWriteEvent) ) { // Error exit. }
// Wait for all reading threads to finish reading.
dwWaitResult = WaitForMultipleObjects( //等待所有读线程完成 NUMTHREADS, // number of handles in array hReadEvents, // array of read-event handles TRUE, // wait until all are signaled INFINITE); // indefinite wait
switch (dwWaitResult) { // All read-event objects were signaled. case WAIT_OBJECT_0: // Write to the shared buffer. break;
// An error occurred. default: printf("Wait error: %d\n", GetLastError()); ExitProcess(0); }
// Set hGlobalWriteEvent to signaled.
if (! SetEvent(hGlobalWriteEvent) ) { // Error exit. }
// Set all read events to signaled. for(i = 1; i <= NUMTHREADS; i++) if (! SetEvent(hReadEvents[i]) ) { // Error exit. } } 3.这是读取线程的代码: VOID ThreadFunction(LPVOID lpParam) { DWORD dwWaitResult; HANDLE hEvents[2];
hEvents[0] = *(HANDLE*)lpParam; // thread's read event hEvents[1] = hGlobalWriteEvent;
dwWaitResult = WaitForMultipleObjects( 2, // number of handles in array hEvents, // array of event handles TRUE, // wait till all are signaled INFINITE); // indefinite wait
switch (dwWaitResult) {
// Both event objects were signaled. case WAIT_OBJECT_0: // Read from the shared buffer. break;
// An error occurred. default: printf("Wait error: %d\n", GetLastError()); ExitThread(0); }
// Set the read event to signaled.
if (! SetEvent(hEvents[0]) ) { // Error exit. } }
--------------------------------------------------------------------------------
这个例子良好的实现了要求的功能,也并不麻烦,但我们想使用更加简洁的方法,实现一个类,可以类似以下的方式使用 g_swmrg.WaitToRead(); 读取... g_swmrg.DoneRead();
g_swmrg.WaitToWrite(); 写入... g_swmrg.DoneRead();
使用关键代码段无疑是互斥最简单的方法,但是却不适用于本文,因为这样的话在读线程之间也有了互斥关系,造成了效率的下降。
看来必须使用两个以上的互斥对象,当代码试图写资源时,他要保证没有其他线程读取和写入 当代码试图读取时,他要保证没有资源正在写入: 我们使用两个Event对象:m_wlock(写入锁),m_rlock(读取锁),这两个对象初始化值是signaled state,不使用自动方式 m_rlock=::CreateEvent(NULL, TRUE, TRUE, NULL);
//---------------------------------------------------- 1.等待写 void WaitToWrite() { ::EnterCriticalSection(&m_cs); DWORD dwWaitResult = ::WaitForSingleObject( m_rlock ,INFINITE ); if( dwWaitResult == WAIT_OBJECT_0 ) ::ResetEvent( m_wlock);//不可以读 else printf( "WaitWrite Error!\n"); ::LeaveCriticalSection(&m_cs); } 2.写完成 void EndWrite() { ::SetEvent( m_wlock ); //唤醒等待的读取线程 } 3.等待读 void WaitToRead() { ::EnterCriticalSection(&m_cs); DWORD dwWaitResult = ::WaitForSingleObject( m_wlock ,INFINITE ); if( dwWaitResult == WAIT_OBJECT_0 ) { ::ResetEvent( m_rlock ); InterlockedIncrement((long*)&m_nReadNum);//m_nReadNum++这个表示有多少个线程正在读 } else printf( "WaitWrite Error!\n"); ::LeaveCriticalSection(&m_cs); } 4.读取完成 void EndRead() { ::EnterCriticalSection(&m_cs); if( 0 >= InterlockedDecrement( (long*)&m_nReadNum )) //m_nReadNum--; ::SetEvent( m_rlock );//当没有线程读时,//唤醒等待的写线程 } 这个例子已经可以正常工作了,但是我们发现了一个问题,当多个线程读取时,因为写入线程要到活动的读取线程数目为0时才可以写入,将会很难得到机会调用,甚至会饿死,为解决这个问题,我们可以在写入线程等待前设置一个事件(老师要挂一块牌子),当读取线程发现这个事件时就等待,修改的代码如下:
--------------------------------------------------------------------------------
// SRWM1.h: interface for the CSRWM1 class. // //////////////////////////////////////////////////////////////////////
#if !defined(AFX_SRWM1_H__62A82971_1C04_4EC9_8A52_C0392E333575__INCLUDED_) #define AFX_SRWM1_H__62A82971_1C04_4EC9_8A52_C0392E333575__INCLUDED_
#if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include <windows.h> #include <stdio.h>
class CSRWM1 { private: HANDLE m_rlock;//读锁 HANDLE m_wlock[2];//写锁 CRITICAL_SECTION m_cs;//关键段 volatile long m_nReadNum; public: //----------------------------------- CSRWM1() { m_rlock=::CreateEvent(NULL, TRUE, TRUE, NULL); m_wlock[0]=::CreateEvent(NULL, TRUE, TRUE, NULL); m_wlock[1]=::CreateEvent(NULL, TRUE, TRUE, NULL); ::InitializeCriticalSection( &m_cs ); if( m_rlock == NULL )perror("m_rlock init Error!\n"); if( m_wlock[0] == NULL )perror("m_wlock init Error!\n"); if( m_wlock[1] == NULL )perror("m_wlock init Error!\n"); m_nReadNum=0; } virtual ~CSRWM1() { ::CloseHandle( m_rlock ); ::CloseHandle( m_wlock[0] ); ::CloseHandle( m_wlock1] ); ::DeleteCriticalSection( &m_cs ); } //-------------------------------------- void WaitToWrite() { ::EnterCriticalSection(&m_cs); ::ResetEvent(m_wlock[1]); DWORD dwWaitResult = ::WaitForSingleObject( m_rlock ,INFINITE ); if( dwWaitResult == WAIT_OBJECT_0 ) ::ResetEvent( m_wlock[0] ); else printf( "WaitWrite Error!\n"); ::LeaveCriticalSection(&m_cs); } void EndWrite() { ::SetEvent( m_wlock[1] ); ::SetEvent( m_wlock[0] ); } //---------------------------------------- void WaitToRead() { ::EnterCriticalSection(&m_cs); DWORD dwWaitResult = ::WaitForMultipleObjects( 2, m_wlock ,TRUE ,INFINITE ); if( dwWaitResult == WAIT_OBJECT_0 ) { ::ResetEvent( m_rlock ); InterlockedIncrement((long*)&m_nReadNum); } else printf( "WaitWrite Error!\n"); ::LeaveCriticalSection(&m_cs); } void EndRead() { if( 0 >= InterlockedDecrement( (long*)&m_nReadNum )) ::SetEvent( m_rlock ); } };
#endif // !defined(AFX_SRWM1_H__62A82971_1C04_4EC9_8A52_C0392E333575__INCLUDED_)

|