现在的位置: 首页 > 综合 > 正文

ACE中文文档中关于互斥锁的非常精妙的一段 与大家共享

2013年09月28日 ⁄ 综合 ⁄ 共 4790字 ⁄ 字号 评论关闭

4.4 通过OOC++简化并发编程

 

这一部分检查一个使用实例,以演示在C++包装中封装Solaris并发机制的优点。该使用实例描述了一个基于生产系统的有代表性的情景[25]。紧跟4.5对库接口的介绍,在4.6中还有ACE OO线程封装类库的其他例子。

通过归纳系统开发中发生的实际问题的解决方案,许多有用的C++类已逐渐发展起来。但是在类的接口和实现稳定后,随着时间的过去,这样的反复对类进行归纳的过程已不再被强调。这让人遗憾,因为要进入面向对象设计和C++的主要障碍是(1)学习并使怎样识别和描述类和对象的过程内在化,以及(2)理解何时以及怎样应用(或不应用)像模板、继承、动态绑定和重载这样的C++特性来简化和归纳他们的程序。

为努力把握C++类设计演变的动力特性,下面的部分演示逐步应用面向对象技术和C++习语、以解决一个惊人地微妙的问题的过程。该问题发生在开发并发分布式应用族的过程中,该应用族在单处理器和多处理器平台上都能高效地执行。通过使用模板和重载来透明地将同步机制参数化进并发应用,这一部分集中考察那些涉及对已有代码进行归纳的步骤。其基础代码基于在[1, 26, 20]中描述的自适配通信环境(ACE)构架中的组件。

此例子检查若干C++的语言特性,它们可以更为优雅地解决4.3.5.1提出的序列化问题。如在那里所描述的,原来的方案既不优雅、不可移植、易错,并且还要求强制性地改变源代码。这一部分演示一种进化的、在先前反复的设计演变中所产生的洞见之上构建的C++方案。

 

4.4.1 初始C++方案

 

解决原来问题的一种更为优雅一点的方案是通过C++ Thread_Mutex包装来封装现有的Solaris mutex_t操作,如下所示:

 

class Thread_Mutex

{

public:

Thread_Mutex (void)

{

mutex_init (&lock_, USYNC_THREAD, 0);

}

 

?Thread_Mutex (void)

{

mutex_destroy (&lock_);

}

 

int acquire (void)

{

return mutex_lock (&lock_);

}

 

int release (void)

{

return mutex_unlock (&lock_);

}

 

private:

// Solaris 2.x serialization mechanism.

mutex_t lock_;

};

 

给互斥机制定义C++包装接口的一个优点是应用代码现在变得更为可移植了。例如,下面是Thread_Mutex类接口的实现,它基于Windows NT WIN32 API[4]中的机制之上:

 

class Thread_Mutex

{

public:

Thread_Mutex (void)

{

InitializeCriticalSection (&lock_);

}

 

?Thread_Mutex (void)

{

DeleteCriticalSection (&lock_);

}

 

int acquire (void)

{

EnterCriticalSection (&lock_); return 0;

}

 

int release (void)

{

LeaveCriticalSection (&lock_); return 0;

}

 

private:

// Win32 serialization mechanism.

CRITICAL_SECTION lock_;

};

 

使用Thread_Mutex C++包装类使原来的代码变得更为清晰,改善了可移植性,并且确保了Thread_Mutex对象被定义时,初始化工作自动地进行。如下面的代码段所示:

 

4-3

 

typedef u_long COUNTER;

// At file scope.

static COUNTER request_count;

// Thread_Mutex protecting request_count.

static Thread_Mutex m;

 

void *run_svc (void *)

{

for (int i = 0; i < iterations; i++)

{

m.acquire ();

 

// Count # of requests.

++request_count;

 

m.release ();

}

 

return (void *) iterations;

}

 

但是,C++封装方法并没有解决所有在4.3.5.1所标出的问题。特别地,它没有解决忘记释放互斥体的问题(它还是需要程序员的人工干预)。此外,使用Thread_Mutex也还是需要对原来的非线程安全的代码进行强制性的改变。

 

4.4.2 另一种C++方案

 

确保锁被自动释放的一种直截了当的方法是使用C++类构造器和析构器语义。下面的工具类使用这些语言构造来自动进行互斥体的获取和释放:

 

class Guard

{

public:

Guard (const Thread_Mutex &m): lock_ (m)

{

lock_.acquire ();

}

 

?Guard (void)

{

lock_.release ();

}

 

private:

const Thread_Mutex &lock_;

}

 

Guard定义了一“块”代码,在其上一个Thread_Mutex被自动获取,并在退出代码块时自动释放。它采用了一种通常称为“作为资源获取的构造器椬魑试词头诺奈龉蛊鳌?/FONT>[9, 27, 7]的C++习语。

如上面的代码所示,当Guard类的对象被创建时,它的构造器自动获取Thread_Mutex对象上的锁。同样地,Guard类的析构器在对象出作用域时自动解锁Thread_Mutex对象。

注意Guard类的数据成员lock_是Thread_Mutex对象的一个引用。这避免了在Guard对象的构造器和析构器每次执行时创建和销毁底层Solaris mutex_t变量的开销。

通过对代码作出轻微的变动,我们现在保证了Thread_Mutex被自动获取和释放:

 

4-4

 

typedef u_long COUNTER;

// At file scope.

static COUNTER request_count;

// Thread_Mutex protecting request_count.

static Thread_Mutex m;

 

void *run_svc (void *)

{

for (int i = 0; i < iterations; i++)

{

{

// Automatically acquire the mutex.

Guard monitor (m);

 

++request_count;

// Automatically release the mutex.

}

 

// Remainder of service processing omitted.

}

}

 

但是,该方案还是没有解决强制性改变代码的问题。而且,在Guard周围增加额外的{}花括号分隔符块既不优雅又容易出错。进行维护的程序员可能会误认为花括号并不重要并将它们去除,产生出下面的代码:

 

for (int i = 0; i < iterations; i++)

{

Guard monitor (m);

 

++request_count;

// Remainder of service processing omitted.

}

 

遗憾的是,这样的“花括号省略”有副作用:它通过序列化主事件循环、消除了应用中所有并发执行。因此,所有应该在那段代码区中并行执行的计算都会被不必要地序列化。

 

4.4.3 改良的C++方案

 

要以一种透明的、非强制的和高效的方式解决现存的问题,需要使用两种另外的C++特性:参数化类型和操作符重载。我们使用这些特性来提供一个称为Atomic_Op的模板类,其部分代码显示如下(完整的接口见4.5.6.2):

 

template <class TYPE>

class Atomic_Op

{

public:

Atomic_Op (void) { count_ = 0; }

Atomic_Op (TYPE c) { count_ = c; }

TYPE operator++ (void)

{

Guard monitor (lock_);

return ++count_;

}

operator TYPE ()

{

Guard monitor_ (lock_);

return count_;

}

// Other arithmetic operations omitted...

 

private:

Thread_Mutex lock_;

TYPE count_;

};

 

Atomic_Op类重新定义了普通的针对内建数据类型的算术操作符(比如++、--、+=,等等),以使这些操作符原子地工作。一般而言,由于C++模板的“延期实例化”语义,任何定义了基本算术操作符的类都将与Atomic_Op类一起工作。

因为Atomic_Op类使用了Thread_Mutex的互斥特性,针对Atomic_Op的实例化对象的算术运算现在在多处理器上工作正常。而且,C++特性(比如模板和操作符重载)还允许这样的技术在多处理器上透明地工作。此外,Atomic_Op中的所有方法操作都被定义为内联函数。因此,一个C++优化编译器将生成代码确保Atomic_Op的运行时性能不会低于直接调用mutex_lock和mutex_unlock函数。

使用Atomic_Op类,我们现在可以编写下面的代码,几乎等同于原来的非线程安全代码(实际上,只是改变了COUNTER的类型定义):

 

4-5

 

typedef Atomic_Op<u_long> COUNTER;

// At file scope

static COUNTER request_count;

 

void *run_svc (void *)

{

for (int i = 0; i < iterations; i++)

{

// Actually calls Atomic_Op::operator++()

++request_count;

}

}

 

通过结合C++构造器/析构器习语(以自动获取和释放Thread_Mutex)和模板及重载的使用,我们生成了一种既简单又非常有表现力的参数化类抽象。该抽象可在无数需要原子操作的类型族上正确而原子地运作。例如,要为其他算术类型提供同样的线程安全功能,我们只需简单地实例化Atomic_Op模板类的新对象:

 

Atomic_Op<double> atomic_double;

Atomic_Op<Complex> atomic_complex;

 

4.4.4 通过参数化互斥机制类型扩展Atomic_Op

 

尽管上面描述的Atomic_op和Guard类的设计产生了正确和透明的线程安全程序,还是存在着足资改进的空间。特别地,注意Thread_Mutex数据成员的类型是被硬编码进Atomic_Op类的。既然在C++中可以使用模板,这样的设计决策就是一种不必要的、可以轻易克服的限制。解决方案就是参数化Guard,并增加另一种类型参数给模板类Atomic_Op,如下所示:

 

template <class LOCK>

class Guard

{

// Basically the same as before...

private:

// new data member change.

const LOCK &lock_;

};

 

template <class LOCK, class TYPE>

class Atomic_Op

{

TYPE operator++ (void)

{

Guard<LOCK> monitor (lock_);

return ++count_;

}

// ...

 

private:

LOCK lock_; // new data member

TYPE count_;

};

 

使用这个新类,我们可以在源代码的开始处作出下面的简单变动:

 

typedef Atomic_Op <Thread_Mutex, u_long> COUNTER;

// At file scope.

COUNTER request_count;

 

// ... same as before

 

【上篇】
【下篇】

抱歉!评论已关闭.