boost::thread简要分析(2)

boost::thread简要分析(2)-A:线程同步
2007-05-18 21:09
除了thread,boost ::thread库另一个重要组成部分是mutex,以及工作在mutex上的boost ::mutex ::scoped_lock、condition和barrier,这些都是为实现线程同步提供的。

mutex
boost提供的mutex有 6种:
boost ::mutex
boost ::try_mutex
boost ::timed_mutex
boost ::recursive_mutex
boost ::recursive_try_mutex
boost ::recursive_timed_mutex
下面仅对boost ::mutex进行分析。
mutex类是一个CriticalSection(临界区)封装类,它在构造函数中新建一个临界区并InitializeCriticalSection,然后用一个成员变量
void * m_mutex ;
来保存该临界区结构。
除此之外,mutex还提供了do_lock、do_unlock等方法,这些方法分别调用EnterCriticalSection、LeaveCriticalSection来修改成员变量m_mutex(CRITICAL_SECTION结构指针)的状态,但这些方法都是 private的,以防止我们直接对mutex进行锁操作,所有的锁操作都必须通过mutex的友元类detail ::thread ::lock_ops <mutex >来完成,比较有意思的是,lock_ops的所有方法:lock、unlock、trylock等都是 static的,如lock_ops <Mutex >::lock的实现:
template < typename Mutex >
class lock_ops : private noncopyable
{
...

public :
     static void lock (Mutex & m )
     {

         m .do_lock ();
     }
...
}

boost ::thread的设计者为什么会这么设计呢?我想大概是:
1、boost ::thread的设计者不希望被我们直接操作mutex,改变其状态,所以mutex的所有方法都是 private的(除了构造函数,析构函数)。
2、虽然我们可以通过lock_ops来修改mutex的状态,如:
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/detail/lock.hpp>

int main ()
{

     boost ::mutex mt ;
     //mt.do_lock();         // Error! Can not access private member!

     boost ::detail ::thread ::lock_ops <boost ::mutex >::lock (mt );

     return 0 ;
}

但是,这是不推荐的,因为mutex、scoped_lock、condition、barrier是一套完整的类系,它们是相互协同工作的,像上面这么操作没有办法与后面的几个类协同工作。

scoped_lock
上面说过,不应该直接用lock_ops来操作mutex对象,那么,应该用什么呢?答案就是scoped_lock。与存在多种mutex一样,存在多种与mutex对应的scoped_lock:
scoped_lock
scoped_try_lock
scoped_timed_lock
这里我们只讨论scoped_lock。
scoped_lock是定义在 namespace boost ::detail ::thread下的,为了方便我们使用(也为了方便设计者),mutex使用了下面的 typedef:
typedef detail ::thread ::scoped_lock <mutex > scoped_lock ;
这样我们就可以通过:
boost ::mutex ::scoped_lock
来使用scoped_lock类模板了。
由于scoped_lock的作用仅在于对mutex加锁 /解锁(即使mutex EnterCriticalSection /LeaveCriticalSection),因此,它的接口也很简单,除了构造函数外,仅有lock /unlock /locked(判断是否已加锁),及类型转换操作符 void *,一般我们不需要显式调用这些方法,因为scoped_lock的构造函数是这样定义的:
explicit scoped_lock (Mutex & mx , bool initially_locked = true )
     :
m_mutex (mx ), m_locked ( false )
{

     if (initially_locked ) lock ();
}

注:m_mutex是一个mutex的引用。
因此,当我们不指定initially_locked参数构造一个scoped_lock对象时,scoped_lock会自动对所绑定的mutex加锁,而析构函数会检查是否加锁,若已加锁,则解锁;当然,有些情况下,我们可能不需要构造时自动加锁,这样就需要自己调用lock方法。后面的condition、barrier也会调用scoped_lock的lock、unlock方法来实现部分方法。
正因为scoped_lock具有可在构造时加锁,析构时解锁的特性,我们经常会使用局部变量来实现对mutex的独占访问。如thread部分独占访问cout的例子:
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>

boost ::mutex io_mutex ;

void count ()      // worker function
{
     for ( int i = 0 ; i < 10 ; ++i )
     {

         boost ::mutex ::scoped_lock lock (io_mutex );
         std ::cout << i << std ::endl ;
     }
}


int main ( int argc , char * argv [])
{

     boost ::thread thrd1 (&count );
     boost ::thread thrd2 (&count );
     thrd1 .join ();
     thrd2 .join ();

     return 0 ;
}

在每次输出信息时,为了防止整个输出过程被其它线程打乱,通过对io_mutex加锁(进入临界区),从而保证了输出的正确性。
在使用scoped_lock时,我们有时候需要使用全局锁(定义一个全局mutex,当需要独占访问全局资源时,以该全局mutex为参数构造一个scoped_lock对象即可。全局mutex可以是全局变量,也可以是类的静态方法等),有时候则需要使用对象锁(将mutex定义成类的成员变量),应该根据需要进行合理选择。
Java的synchronized可用于对方法加锁,对代码段加锁,对对象加锁,对类加锁(仍然是对象级的),这几种加锁方式都可以通过上面讲的对象锁来模拟;相反,在Java中实现全局锁好像有点麻烦,必须将请求封装到类中,以转换成上面的四种synchronized形式之一。

condition
condition的接口如下:
class condition : private boost ::noncopyable     // Exposition only
{
public :
   // construct/copy/destruct
   condition ();
   ~
condition ();

   // notification
   void notify_one ();
   void notify_all ();

   // waiting
   template < typename ScopedLock > void wait (ScopedLock &);
   template < typename ScopedLock , typename Pred > void wait (ScopedLock &, Pred );
   template < typename ScopedLock >
     bool timed_wait (ScopedLock &, const boost ::xtime &);
   template < typename ScopedLock , typename Pred >
     bool timed_wait (ScopedLock &, Pred );
};

其中wait用于等待某个condition的发生,而timed_wait则提供具有超时的wait功能,notify_one用于唤醒一个等待该condition发生的线程,notify_all则用于唤醒所有等待该condition发生的线程。

由于condition的语义相对较为复杂,它的实现也是整个boost ::thread库中最复杂的(对Windows版本而言,对支持pthread的版本而言,由于pthread已经提供了pthread_cond_t,使得condition实现起来也十分简单),下面对wait和notify_one进行简要分析。
condition内部包含了一个condition_impl对象,由该对象执行来处理实际的wait、notify_one ...等操作。

下面先对condition_impl进行简要分析。
condition_impl在其构造函数中会创建两个Semaphore(信号量):m_gate、m_queue,及一个Mutex(互斥体,跟boost ::mutex类似,但boost ::mutex是基于CriticalSection <临界区 >的):m_mutex,其中:
m_queue
相当于当前所有等待线程的等待队列,构造函数中调用CreateSemaphore来创建Semaphore时,lMaximumCount参数被指定为 (std ::numeric_limits < long >::max )(),即便如此,condition的实现者为了防止出现大量等待线程的情况(以至于超过了 long的最大值),在线程因执行condition ::wait进入等待状态时会先:
WaitForSingleObject ( reinterpret_cast <HANDLE >(m_queue ), INFINITE );
以等待被唤醒,但很难想象什么样的应用需要处理这么多线程。
m_mutex
用于内部同步的控制。
但对于m_gate我很奇怪,我仔细研究了一下condition_imp的实现,还是不明白作者引入m_gate这个变量的用意何在,既然已经有了用于同步控制的m_mutex,再引入一个m_gate实在让我有点不解。

以下是condition ::wait调用的do_wait方法简化后的代码:
template < typename M >
void do_wait (M & mutex )
{

     m_impl .enter_wait ();
     lock_ops ::unlock (mutex , state );      //对传入的scoped_lock对象解锁,以便别的线程可以对其进行加锁,并执行某些处理,否则,本线程等待的condition永远不会发生(因为没有线程可以获得访问资源的权利以使condition发生)
     m_impl .do_wait ();      //执行等待操作,等待其它线程执行notify_one或notify_all操作以获得
     lock_ops ::lock (mutex , state );      //重新对scoped_lock对象加锁,获得独占访问资源的权利
}
condition ::timed_wait的实现方法与此类似,而notify_one、notify_all仅将调用请求转发给m_impl,就不多讲了。

虽然condition的内部实现比较复杂,但使用起来还是比较方便的。下面是一个使用condition的多Producer -多Consumer同步的例子(这是本人为即将推出的“大卫的Design Patterns学习笔记”编写的Mediator模式的示例):
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/xtime.hpp>

#include <iostream>
#include <time.h> // for time()

#include <Windows.h>     // for Sleep, change it for other platform, we can use
                         // boost::thread::sleep, but it's too inconvenient.

typedef boost ::mutex ::scoped_lock scoped_lock ;
boost ::mutex io_mutex ;

class Product
{

     int num ;
public :
     Product ( int num ) : num (num ) {}

     friend std ::ostream & operator << (std ::ostream & os , Product & product )
     {

         return os << product .num ;
     }
};


class Mediator
{

private :
     boost ::condition cond ;
     boost ::mutex mutex ;

     Product ** pSlot ;      // product buffer/slot
     unsigned int slotCount ,      // buffer size
         productCount ; // current product count
     bool stopFlag ;      // should all thread stop or not

public :
     Mediator ( const int slotCount ) : slotCount (slotCount ), stopFlag ( false ), productCount ( 0 )
     {

         pSlot = new Product *[slotCount ];
     }


     virtual ~Mediator ()
     {

         for ( int i = 0 ; i < static_cast < int >(productCount ); i ++)
         {

             delete pSlot [i ];
         }

         delete [] pSlot ;
     }


     bool Stop () const { return stopFlag ; }
     void Stop ( bool ) { stopFlag = true ; }

     void NotifyAll ()      // notify all blocked thread to exit
     {
         cond .notify_all ();
     }


     bool Put ( Product * pProduct )
     {

         scoped_lock lock (mutex );
         if (productCount == slotCount )
         {
             {

                 scoped_lock lock (io_mutex );
                 std ::cout << "Buffer is full. Waiting..." << std ::endl ;
             }

             while (!stopFlag && (productCount == slotCount ))
                 cond .wait (lock );
         }

         if (stopFlag ) // it may be notified by main thread to quit.
             return false ;

         pSlot [ productCount ++ ] = pProduct ;
         cond .notify_one ();      // this call may cause *pProduct to be changed if it wakes up a consumer

         return true ;
     }

 

你可能感兴趣的:(thread,Semaphore,delete,Class,buffer,iostream)