随笔之POSIX cond和Windows同步对象Event的讨论

 
一 原因
最近在实现一个线程池的时候,须要用到POSIX中的cond和mutex进行线程间等待和同步,功能相似MS的同步对象Event。
发现cond和mutex的连用仍是挺不人性化的。说实话,MS在同步对象的API上,作得仍是至关不错,文档也很清晰。
Anyway,既然只能使用POSIX,就只能将就了。
我这个线程池在实现中碰到如下2个问题:
1 有n个线程等待一个事件。当有任务添加的时候,须要触发其中一个线程启动。
2 当线程池退出时,我须要触发全部线程启动,并检测退出标志,从而退出线程循环。
这个问题其实比初看上去要复杂,下面来分析
二 Windows上的实现
先介绍下Event同步对象,
HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes, // pointer to security attributes
  BOOL bManualReset,  // flag for manual-reset event
  BOOL bInitialState, // flag for initial state
  LPCTSTR lpName      // pointer to event-object name
);
第二个参数:bManualReset表示该对象为人工仍是自动变量。咱们重点就是讨论这个,该值决定如下几个特色:
  • 当Event为人工变量时,一旦被触发,则一直保持触发状态,触发调用ResetEvent重置状态。假设该值被触发,那么调用WaitXXX函数的全部线程都不该该阻塞在该事件上。
  • 当Event为自动变量时,一旦被触发,若是有线程在等,那么只会启动其中一个线程(只会启动一个线程,由于该值在启动一个线程后会重置)。若是没有线程在等,那么该值一直保持触发状态
从上面Event的介绍来看,自动变量一次触发一个线程,而人工变量一直保持触发状态。不过系统并无说若是一个线程很快SetEvent并又ResetEvent的话,Wait的线程会如何。
 
咱们先看看在MS平台上该如何解决上面的问题:
最广泛的方法就是:
1 建立一个Auto的Event,这个Event用来触发工做线程从任务队列中获取任务
2 建立一个Manual的Event,这个Event用来触发全部工做线程退出
而后利用WaitForMultiObject来等待这两个Event。
Problem solved!!
三 Linux的实现
Linux上最大的问题是没有WaitForMultiObject这样的函数,那么咱们只能建立一个包含Mutex和Cond的结构体来充当Event
因为cond的触发有两个函数,特性分别是:
  • pthread_cond_signal:保证多个线程调用pthread_cond_wait等待的时候只有一个线程可以返回。
  • pthread_cond_broadcast:保证多个线程等待的时候,都能返回。
咱们在使用cond实现类型的触发和等待的时候,代码常常以下所示:
void Wake(allThreads?)
{
   Mutex lock
   set condition = 1;
  if(allThreads)
    pthread_cond_broadcast  //触发全部线程
else
   pthread_cond_signal  //触发单个线程
   Mutex unlock
}
void Wait()
{
   Mutex lock
  while(condition != 1)
   {
      pthread_cond_wait(cond,&mutex); //cond的wait须要传入一个Mutex作参数,wait函数内部会unlock这个mutex
   }
   Mutex unlock
}
 
为何在Wait的时候须要有一个while循环来检测condition是否知足条件呢?最主要有两个缘由:
  • cond自己的机制致使。若是cond触发(不管是signal仍是broadcast)的时候,没有线程在wait的话,那么线程之后wait将没法捕获这个触发。这和Windows的Event大相径庭。自动变量的Event若是触发后,即便当时没有线程在等待,若是之后有线程等待的话,那么也是能够返回的。而posix的cond没有实现这个功能。因此咱们只能本身加condition来作判断。若是condition为1,则无需等待。
  • pthread_cond_wait的返回有多是由于信号的缘由致使,这个时候conditon并不知足,因此这里也须要一个while循环
根据上面的基础知识,咱们目前已知足的是:
  • cond_signal能而且只能触发一个线程起来
  • cond_broadcast能触发全部线程起来
可是问题随之而来,由于如今只有一个condition,何时置为零呢?必须知足两个条件:
  • 对于触发单个线程的来讲,由于只有一个启动,因此当它启动后,须要把condtion置为零,表示本身已经消费了这个条件。
  • 对于全部启动的来讲,只有最后一个线程退出等待的时候须要把conditon置为零,表示全部人都消费了这个条件。
解决办法就是使用waiter计数,最终修改后的代码以下:
void Wake(allThreads?)
{
   Mutex lock
   if(condtion == -1 || condition == 1)  //为何要加这样的判断?
     {
       Mutex unlock
       return;
     }
    if(allThreads)
  {
    set condition = -1; //表示等待多个
     pthread_cond_broadcast  //触发全部线程
  }
else
{
   set condition = 1;
    pthread_cond_signal  //触发单个线程
}
   Mutex unlock
}
void Wait()
{
   Mutex lock
  ++waiters;
  while(condition == 0)
   {
      pthread_cond_wait(cond,&mutex); //cond的wait须要传入一个Mutex作参数,wait函数内部会unlock这个mutex
   }
  if(--waiters == 0)
     condition = 0;
 
   Mutex unlock
}
咱们能够捋一下各类case:
  • 只触发单个线程的话,没有问题。这个是最简单的。
  • 触发多个线程的话,若是在触发后而且在旧的等待线程未所有返还以前,若是又有新线程调用wait的话,其实是不会被阻塞的。
Wake函数前面红色字体的代码干什么用?
就怕调用者先调用了Wake(true),旋即又调用了Wake(false),致使condtion值混乱。经过判断condition是否已经触发,咱们能够避免出问题。
 
四:结论
POSIX的cond和mutex联合使用,总感受效率上会有损失。由于cond_wait返还后仍是须要竞争mutex,实际上Wait函数在用户空间是串行执行的。
不知道Windows的Event是如何实现的?
上面的代码应该还不是很完善,有问题再修改,不知道您看出什么毛病了吗?
相关文章
相关标签/搜索