??xml version="1.0" encoding="utf-8" standalone="yes"?>影音成人av,91精彩视频在线观看,av在线1区2区http://www.aygfsteel.com/killvin/category/15182.htmlzh-cnThu, 01 Mar 2007 15:37:26 GMTThu, 01 Mar 2007 15:37:26 GMT60Double Checked Locking 模式http://www.aygfsteel.com/killvin/archive/2006/09/13/69499.htmlkillvinkillvinWed, 13 Sep 2006 15:16:00 GMThttp://www.aygfsteel.com/killvin/archive/2006/09/13/69499.html

ACE中的Double Checked Locking 模式


(作?Douglas C. Schmidt ,by huihoo.org CORBAN Thzhang ?, Allen整理,制作)

意图

无论什么时候当临界Z的代码仅仅需要加锁一ơ,同时当其获取锁的时候必LU程安全的,可以用Double Checked Locking 模式来减竞争和加锁载荷?

动机

1、标准的单例。开发正的有效的ƈ发应用是困难的。程序员必须学习新的技术(q发控制和防止死锁的法Q和机制Q如多线E和同步APIQ。此外,许多熟悉的设计模式(如单例和q代子)在包含不使用Mq发上下文假讄序E序中可以工作的很好。ؓ了说明这点,考虑一个标准的单例模式在多U程环境下的实现。单例模式保证一个类仅有一个实例同时提供了全局唯一的访问这个实例的入口炏V在c++E序中动态分配单例对象是通用的方式,q是因ؓc++E序没有很好的定义静态全局对象的初始化ơ序Q因此是不可UL的。而且Q动态分配避免了单例对象在永q没有被使用情况下的初始化开销?


class Singleton
{
public:
static Singleton *instance (void)
{
if (instance_ == 0)
// Critical section.
instance_ = new Singleton;
return instance_;
}
void method (void);
// Other methods and members omitted.
private:
static Singleton *instance_;
};



应用代码在用单例对象提供的操作前,通过调用静态的instanceҎ来获取单例对象的引用Q如下所C:
Singleton::instance ()->method ();
2、问题:竞争条g。不q的是,上面展示的标准单例模式的实现在抢先多d和真正ƈ行环境下无法正常工作。例如,如果在ƈ行主Zq行的多个线E在单例对象初始化之前同时调用Singleton::instanceҎQSingleton的构造函数将被调用多ơ,q是因ؓ多个U程在上面展示的界区中执行new singleton操作。界区是一个必遵守下列定式的指o序列Q当一个线E?q程在界区中运行时Q没有其他Q何线E?q程会同时在临界Zq行。在q个例子中,单例的初始化q程是一个界区Q违反界区的原则,在最好的情况下将D内存泄漏Q最坏的情况下,如果初始化过E不是幂{的Qidempotent.Q,导致严重的后果?

3?通常的陷阱和弊端。实C界区的通常Ҏ是在cM增加一个静态的Mutex对象。这个Mutex保证单例的分配和初始化是原子操作Q如下:


class Singleton
{
public:
static Singleton *instance (void)
{
// Constructor of guard acquires lock_ automatically.
Guard guard (lock_);
// Only one thread in the critical section at a time.
if (instance_ == 0)
instance_ = new Singleton;
return instance_;
// Destructor of guard releases lock_ automatically.
}
private:
static Mutex lock_;
static Singleton *instance_;
};


guardcM用了一个c++的习惯用法,当这个类的对象实例被创徏Ӟ它用构造函数来自动获取一个资源,当类对象d一个区域时Q用析构器来自动释放这个资源。通过使用guardQ每一个对Singleton::instanceҎ的访问将自动的获取和释放lock_?
即ɘq个临界区只是被使用了一ơ,但是每个对instanceҎ的调用都必须获取和释放lock_。虽然现在这个实现是U程安全的,但过多的加锁负蝲是不能被接受的。一个明显(虽然不正)的优化方法是guard攑֜针对instanceq行条g的内部Q?


static Singleton *instance (void)
{
if (instance_ == 0) {
Guard guard (lock_);
// Only come here if instance_ hasn't been initialized yet.
instance_ = new Singleton;
}
return instance_;
}

q将减少加锁负蝲Q但是不能提供线E安全的初始化。在多线E的应用中,仍然存在竞争条gQ将D多次初始化instance_。例如,考虑两个U程同时?instance_ == 0Q都会成功Q一个将通过guard获取lock_另一个将被阻塞。当W一U程初始化Singleton后释放lock_Q被d的线E将获取lock_Q错误的再次初始化Singleton?
4、解决之道,Double Checked Locking优化。解册个问题更好的Ҏ是用Double Checked Locking。它是一U用于清除不必要加锁q程的优化模式。具有讽刺意味的是,它的实现几乎和前面的Ҏ一栗通过在另一个条件检中包装对new的调用来避免不必要的加锁Q?


class Singleton
{
public:
static Singleton *instance (void)
{
// First check
if (instance_ == 0)
{
// Ensure serialization (guard constructor acquires lock_).
Guard guard (lock_);
// Double check.
if (instance_ == 0)
instance_ = new Singleton;
}
return instance_;
// guard destructor releases lock_.
}
private:
static Mutex lock_;
static Singleton *instance_;
};


W一个获取lock_的线E将构徏SingletonQƈ指针分配给instance_Q后l调用instanceҎ的线E将发现instance_ != 0Q于是将跌初始化过E。如果多个线E试囑ƈ发初始化SingletonQ第二个gL竞争条g的发生。在上面的代码中Q这些线E将在lock_上排队,当排队的U程最l获取lock_Ӟ他们发现instance_ != 0于是蟩q初始化q程?

上面Singleton::instance的实C仅在Singleton首次被初始化Ӟ如果有多个线E同时进入instanceҎ导致加锁负载。在后箋对Singleton::instance的调用因为instance_ != 0而不会有加锁和解锁的负蝲?通过增加一个mutex和一个二ơ条件检,标准的单例实现可以是U程安全的,同时不会产生q多的初始化加锁负蝲?

适应?/h3>> 当一个应用具有下列特征时Q可以用Double Checked Locking优化模式Q?
1、应用包含一个或多个需要顺序执行的临界Z码?
2、多个线E可能潜在的试图q发执行临界区?
3、界区仅仅需要被执行一ơ?
4、在每一个对临界区的讉Kq行加锁操作导致过多加锁负载?
5、在一个锁的范围内增加一个轻量的Q可靠的条g是可行的?

l构和参与?/h3>通过使用伪代码能够最好地展示Double Checked Locking模式的结构和参与者,?展示了在Double Checked Locking模式有下列参与者:



1、仅有一ơ界区QJust Once Critical Section,Q。界区所包含的代码仅仅被执行一ơ。例如,单例对象仅仅被初始化一ơ。这P执行对new Singleton的调用(只有一ơ)相对于Singleton::instanceҎ的访问将非常E?
2、mutex。锁被用来序列化对界区中代码的讉K?
3、标记。标记被用来指示临界区的代码是否已经被执行过。在上面的例子中单例指针instance_被用来作为标记?
4?应用U程。试图执行界区代码的线E?

协作

?展示了Double Checked Locking模式的参与者之间的互动。作ZU普通的优化用例Q应用线E首先检flag是否已经被设|。如果没有被讄Qmutex被获取。在持有q个锁之后,应用U程再ơ检flag是否被设|,实现Just Once Critical SectionQ设定flag为真。最后应用线E释N?



l论

使用Double Checked Locking模式带来的几点好处:
1、最化加锁。通过实现两个flag,Double Checked Locking模式实现通常用例的优化。一旦flag被设|,W一个检将保证后箋的访问不要加锁操作?
2、防止竞争条件。对flag的第二个将保证临界Z的事件仅实现一ơ?
使用Double Checked Locking模式也将带来一个缺点:产生微妙的移植bug的潜能。这个微妙的UL问题能够D致命的bugQ如果用Double Checked Locking模式的Y件被UL到没有原子性的指针和正数赋D义的gq_上。例如,如果一个instance_指针被用来作为Singleton实现的flagQinstance_指针中的所有位QbitQ必d一ơ操作中完成d写。如果将new的结果写入内存不是一个原子操作,其他的线E可能会试图d一个不健全的指针,q将D非法的内存访问?
在一些允许内存地址跨越寚w边界的系l上q种现象是可能的Q因此每ơ访问需要从内存中取两次。在q种情况下,pȝ可能使用分离的字寚w合成flagQ来表示instance_指针?
如果一个过于激q(aggressiveQ编译器通过某种~冲手段来优化flagQ或是移除了W二个flag==0,带来另外的相关问题。后面会介绍如何使用volatile关键字来解决q个问题?

实现和例子代?/h3>ACE在多个库lg中用Double Checked Locking模式。例如,Z减少代码的重复,ACE使用了一个可重用的适配器ACE Singleton来将普通的c{换成h单例行ؓ的类。下面的代码展示了如何用Double Checked Locking模式来实现ACE Singleton?


// A Singleton Adapter: uses the Adapter
// pattern to turn ordinary classes into
// Singletons optimized with the
// Double-Checked Locking pattern.
template 
class ACE_Singleton
{
public:
static TYPE *instance (void);
protected:
static TYPE *instance_;
static LOCK lock_;
};
template  TYPE *
ACE_Singleton::instance ()
{
// Perform the Double-Checked Locking to
// ensure proper initialization.
if (instance_ == 0) {
ACE_Guard lock (lock_);
if (instance_ == 0)
instance_ = new TYPE;
}
return instance_;
}


ACE Singletonc被TYPE和LOCK来参数化。因此一个给定TYEP的类被转换成用LOCKcd的互斥量的具有单例行为的cR?
ACE中的Token Manager.是用ACE Singleton的一个例子。Token Manager实现在多U程应用中对本地和远端的tokenQ一U递归锁)死锁。ؓ了减资源的使用QToken Manager被按需创徏。ؓ了创Z个单例的Token Manager对象Q只是需要实C面的typedefQ?

typedef ACE_Singleton Token_Mgr;
Token Manager单例被用于本地和远端的token死锁。在一个线E阻塞等待互斥量之前Q它首先查询Token Manager单例Q来试d是否会导致死锁状态。对于系l中的每一个tokenQToken Manager单例l护一个持有tokenU程和所有阻塞等待在该token的线E记录链表。这些数据将提供充的检死锁状态的依据。用Token Manager单例的过E如下:


// Acquire the mutex.
int Mutex_Token::acquire (void)
{
// If the token is already held, we must block.
if (mutex_in_use ()) {
// Use the Token_Mgr Singleton to check
// for a deadlock situation *before* blocking.
if (Token_Mgr::instance ()->testdeadlock ()) {
errno = EDEADLK;
return -1;
				}
else
// Sleep waiting for the lock...
		// Acquire lock...
		}

变化

一U变化的Double Checked Locking模式实现可能是需要的Q如果一个编译器通过某种~冲方式优化了flag。在q种情况下,~冲的粘着性(coherencyQ将变成问题Q如果拷贝flag到多个线E的寄存器中Q会产生不一致现象。如果一个线E更改flag的值将不能反映在其他线E的对应拯中?

另一个相关的问题是编译器U除了第二个flag==0,因ؓ它对于持有高度优化特性的~译器来说是多余的。例如,下面的代码在Ȁq的~译器下被跌对flag的读取,而是假定instance_q是?Q因为它没有被声明ؓvolatile的?

Singleton *Singleton::instance (void)
{
if (Singleton::instance_ == 0)
{
// Only lock if instance_ isn't 0.
Guard guard (lock_);
// Dead code elimination may remove the next line.
// Perform the Double-Check.
if (Singleton::instance_ == 0)
// ...



解决q两个问题的一个方法是生命flag为Singleton的volatile成员变量Q如下:
private:
static volatile long Flag_; // Flag is volatile.
使用volatile保证编译器不会flag~冲到编译器Q同时也不会优化掉第二次L作。用volatile关键字的a下之意是所有对flag的访问是通过内存Q而不是通过寄存器?

相关模式

Double Checked Locking模式是First-Time-In习惯用法的一个变化。First-Time-In习惯用法l常使用在类似cq种~少构造器的程序语a中,下面的代码展CZq个模式Q?


	static const int STACK_SIZE = 1000;
static T *stack_;
static int top_;
void push (T *item)
{
// First-time-in flag
if (stack_ == 0) {
stack_ = malloc (STACK_SIZE * sizeof *stack);
assert (stack_ != 0);
top_ = 0;
}
stack_[top_++] = item;
// ...
}



W一ơpush被调用时Qstack_?Q这导致触发malloc来初始化它自己?


killvin 2006-09-13 23:16 发表评论
]]> վ֩ģ壺 | ܿ| ӽ| ¸| ͤ| ɽ| | ͩ| IJ| Т| | | ɳ| | | | ľ˹| | ֹ| ۷| | | | | | Ͽ| ˴| | ɽ| | ܿ| | ٰ| ױ| ʦ| | | | | ؿ˹| |