Win32下两种用于C++的线程同步类(下)
当前位置:以往代写 > C/C++ 教程 >Win32下两种用于C++的线程同步类(下)
2019-06-13

Win32下两种用于C++的线程同步类(下)

Win32下两种用于C++的线程同步类(下)

副标题#e#

上一篇中我先容了一种通过关闭Critical Section工具而利便的利用互斥锁的方法,文中所有的例子是两个线程对同一数据一读一写,因此需要让它们在这里互斥,不能同时会见。而在实际环境中大概会有更巨大的环境呈现,就是多个线程会见同一数据,一部门是读,一部门是写。我们知道只有读-写或写-写同时举办时大概会呈现问题,而读-读则可以同时举办,因为它们不会对数据举办修改,所以也有须要在C++中封装一种利便的答允读-读并发、读-写与写-写互斥的锁。要实现这种锁,利用临界区就很坚苦了,不如改用内核工具,这里我利用的是互斥量(Mutex)。

总体的布局与上一篇中的雷同,都是写出一个对锁举办封装的基类,再写一个用于挪用加、解锁函数的类,通过对第二个类的生命周期的打点实现加锁息争锁。这里涉及到两个新问题,一是加锁、解锁行动都有两种,一种是加/解读锁,一种是加/解写锁;二是为了答允读-读并发,这里只声明一个Mutex是不足的,必需要声明多个Mutex,并且有几多个Mutex就同时答允几多个读线程并发,之所以这么说,是因为我们要利用的API函数是WaitForMultipleObjects。

WaitForMultipleObjects函数的成果就是期待工具状态被配置,MSDN中对它的说明为:

Waits until one or all of the specified objects are in the signaled state or the time-out interval elapses.

这是个很好用的函数,我们可以用它来期待某个或某几个工具,而且答允配置超时时间,期待乐成时与超时时返回的值是差异的。假如返回的值比WAIT_ABANDONED小则暗示期待乐成。“期待乐成”对付差异范例的内核工具有差异的意义,譬喻对付历程或线程工具,期待乐成绩暗示历程或线程执行竣事了;对付互斥量工具,则暗示此工具此刻不被任何其他线程拥有,而且一旦期待乐成,当前线程即拥有了此互斥量,其他线程则不能同时拥有,直接挪用ReleaseMutex函数主动释放互斥量。


#p#副标题#e#

与WaitForMultipleObjects雷同的尚有一个函数WaitForSingleObject,它的成果较量简朴,只针对单一个工具,而WaitForMultipleObjects可以同时期待多个工具,而且可以配置是否期待所有工具。

上一篇文章顶用的InstanceLockBase类内里封装了一个Critical Section工具,这里则要封装一组Mutex的Handle,那么这一组是几多个呢?它应该由利用此类的措施中界说,譬喻可以用动态数组的要领:

//基类:
class RWLockBase //暗示Read/Write Lock
...{
 HANDLE* handles;
 protected:
  RWLockBase(int handleCount) ...{ handles = new HANDLE[handleCount]; }
 …
};
//子类:
class MyClass: public RWLockBase
...{
 MyClass(): RWLockBase(3) ...{}
 …
};

这确实是个不错的步伐,通过在子类结构函数的初始化段中挪用基类结构函数并传参,使得这个动态数组得以正确初始化,不外这样看着不太爽,子类必需两次呈现“RWLockBase”一词,能不能像InstanceLockBase那样只要担任了就好呢?谜底是必定的,只要用C++模板即可:

template <int maxReadCount>
class RWLockBase
...{
 HANDLE handles[maxReadCount];
 …
};

利用模板附带这么一个长处,因为模板参数是在编译期可以确定的,所以无需再用动态数组,直接在栈上分派即可。而利用模板引出一个新问题,就是相应的Lock类(RWLock)在结构时传的工具指针时的范例声明,直接写成RWLock(RWLockBase* pObj)必定是不可的,因为必需指定模板参数,而且其值还必需与声明RWLockBase时所指定的值一致才行,从而客户端代码就必需两次指定模板参数值,不爽!办理的步伐也是有一个,就是把RWLockBase酿成夹层类,为它再声明一个基类,让RWLock吸收的是基类指针,并把Lock、Unlock等函数放在基类中,声明为纯虚函数,实现写在夹层类中:

#p#副标题#e# class _RWLockBase
...{
 friend class RWLock;
 protected:
  virtual DWORD ReadLock(int timeout) = 0;
  virtual void ReadUnlock(int handleIndex) = 0;
  virtual DWORD WriteLock(int timeout) = 0;
  virtual void WriteUnlock() = 0;
};

模板类RWLockBase从_RWLockBase担任,并对四个函数写出实现:

template <int maxReadCount = 3> //这里给一个缺省参数,只管淘汰客户端代码量
class RWLockBase: public _RWLockBase
...{
 HANDLE handles[maxReadCount];
 DWORD ReadLock(int timeout) //加读锁,只要比及一个互斥量返回即可
 ...{
  return ::WaitForMultipleObjects(maxReadCount, handles, FALSE, timeout);
 }
 void ReadUnlock(int handleIndex) //解读锁,释放已得到的互斥量
 ...{
  ::ReleaseMutex(handles[handleIndex]);
 }
 DWORD WriteLock(int timeout) //加写锁,比及所有互斥量,从而与其他所有线程互斥
 ...{
  return ::WaitForMultipleObjects(maxReadCount, handles, TRUE, timeout);
 }
 void WriteUnlock() //解写锁,释放所有的互斥量
 ...{
  for(int i = 0; i < maxReadCount; i++)
   ::ReleaseMutex(handles[i]);
 }
 protected:
  WLockBase() //结构函数,初始化每个互斥量
  ..{
   for(int i = 0; i < maxReadCount; i++)
    handles[i] = ::CreateMutex(0, FALSE, 0);
  }
  ~RWLockBase() //析构函数,销毁工具
  ...{
  for(int i = 0; i < maxReadCount; i++)
   ::CloseHandle(handles[i]);
  }
};

#p#分页标题#e#

而相应的锁类也会稍巨大一些:

#p#副标题#e# class RWLock
...{
 bool lockSuccess; //因为有大概超时,需要生存是否期待乐成
 int readLockHandleIndex; //对付读锁,需要知道得到的是哪个互斥量
 _RWLockBase* _pObj; //方针工具基类指针
 public:
  //这里通过第二个参数抉择是加读锁照旧写锁,第三个参数为超时的时间
  RWLock(_RWLockBase* pObj, bool readLock = true, int timeout = 3000)
  ...{
   _pObj = pObj;
   lockSuccess = FALSE;
   readLockHandleIndex = -1;
   if(NULL == _pObj)
    return;
   if(readLock) //读锁
   ...{
    DWORD retval = _pObj->ReadLock(timeout);
    if(retval < WAIT_ABANDONED) //返回值小于WAIT_ABANDONED暗示乐成
    ...{ //其值减WAIT_OBJECT_0就是数组下标
     readLockHandleIndex = retval - WAIT_OBJECT_0;
     lockSuccess = TRUE;
    }
   }
   else
   ...{
 WORD retval = _pObj->WriteLock(timeout);
 if(retval < WAIT_ABANDONED) //写锁时得到了所有互斥量,无需生存下标
  lockSuccess = TRUE;
 }
}
~RWLock()
...{
if(NULL == _pObj)
 return;
 if(readLockHandleIndex > -1)
 _pObj->ReadUnlock(readLockHandleIndex);
else
 _pObj->WriteUnlock();
}
bool IsLockSuccess() const ...{ return lockSuccess; }
}; 

这样一来,读/写锁的类也就完成了,利用时与InstanceLock雷同:

#p#副标题#e#

1、被锁工具从RWLockBase<>类担任

2、需要加读锁时,声明一个RWLock实例,并指出要加的是读锁

3、需要加写锁时,声明一个RWLock实例,并指出要加的是写锁

这里照旧要多说两句,固然利用纯虚函数团结模板类,使得客户端代码量减到最少,但机能上有一些影响,因为声明白虚函数,则实例中一定存在4个字节的VPTR,挪用虚函数时则要查找VTABLE,空间和时间上都有微小的牺牲。而假如不利用模板类,则没有虚函数的价钱,但也有牺牲:不利用模板类则需要利用动态数组,动态数组自己需要措施运行时在堆上分派,这也需要时间;指向动态数组的指针也需要占用内存,所以空间上的开锁是一样的,时间上固然动态分派内存需要的时间应该比虚函数的挪用要慢一点,但初始化只需要一次,总体来说也是值得的。所以最终要利用哪一种,就看详细需要了。

这里也给出一个尝试。这里所用的被锁类也上一篇雷同,简朴的从RWLockBase类担任:

class MyClass2: public RWLockBase<>
...{};
MyClass2 mc2;

看看两个线程函数:

//读线程
DWORD CALLBACK ReadThreadProc(LPVOID param)
...{
 int i = (int)param;
 RWLock lock(&mc2); //加读锁
 if(lock.IsLockSuccess()) //假如加锁乐成
 {
  Say("read thread %d started", i); //为了代码短一些,假设Say函数有这种本领
  Sleep(1000);
  Say("read thread %d ended", i);
 }
 else //加锁超时,则显示超时信息
  Say("read thread %d timeout", i);
  return 0;
}
//写线程
DWORD CALLBACK WriteThreadProc(LPVOID param)
...{
 int i = (int)param;
 RWLock lock(&mc2, false); //加写锁。
 if(lock.IsLockSuccess())
 ...{
  Say("write thread %d started", i);
  Sleep(600);
  Say("write thread %d ended", i);
 }
 else
  Say("write thread %d timeout", i);
  return 0;
}

主线程:

#p#副标题#e# int i;
for(i = 0; i < 5; i++)
::CreateThread(0, 0, ReadThreadProc, (LPVOID)i, 0, 0);
for(i = 0; i < 5; i++)
::CreateThread(0, 0, WriteThreadProc, (LPVOID)i, 0, 0);

#p#分页标题#e#

措施共开10个线程,5个读5个写。从RWLockBase类担任时我们利用了默认的模板参数,所以最多同时答允3个读线程。措施的运行功效如下:

001 [15:07:28.484]read thread 0 started
002 [15:07:28.484]read thread 1 started
003 [15:07:28.484]read thread 2 started
004 [15:07:29.484]read thread 0 ended
005 [15:07:29.484]read thread 3 started
006 [15:07:29.484]read thread 1 ended
007 [15:07:29.484]read thread 4 started
008 [15:07:29.484]read thread 2 ended
009 [15:07:30.484]read thread 3 ended
010 [15:07:30.484]read thread 4 ended
011 [15:07:30.484]write thread 0 started
012 [15:07:31.078]write thread 0 ended
013 [15:07:31.078]write thread 1 started
014 [15:07:31.484]write thread 2 timeout
015 [15:07:31.484]write thread 3 timeout
016 [15:07:31.484]write thread 4 timeout
017 [15:07:31.687]write thread 1 ended

前三行三个读线程取得读锁,之后等一秒(第4-8行),三个读线程都竣事了,而且余下的两个读线程取得读锁,固然这时剩下了一个互斥量没有利用,但因为其他的线程都请求加写锁,写锁与其他所有线程互斥,所以还不能取得写锁。再过一秒(第9-11行),厥后的两个取得读锁的线程也竣事了,则第一个写线程取得写锁。600毫秒之后(第12-13行)第一个写线程竣事,第二个写线程开始。400毫秒之后(第14-16行)余下的三个写线程都超时了,再后第二个写线程也竣事了。

    关键字:

在线提交作业