ITPub博客

首页 > 应用开发 > IT综合 > 线程同步的一些常见模式(1) (转)

线程同步的一些常见模式(1) (转)

原创 IT综合 作者:amyz 时间:2007-08-14 15:54:05 0 删除 编辑
线程同步的一些常见模式(1) (转)[@more@]

本篇文章说明了一种多线程编程中常见的模式,该模式主要描述如下:
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 ", 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 ", 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! ");
  ::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! ");
  ::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

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! ");
  if( m_wlock[0] == NULL )perror("m_wlock init Error! ");
  if( m_wlock[1] == NULL )perror("m_wlock init Error! ");
  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! ");
  ::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! ");
  ::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_)


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/10752019/viewspace-956319/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
  • 博文量
    3982
  • 访问量
    7895578