分享
 
 
 

注意:双检测锁定并不可靠

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

最近看了一些关于模式实现的文章,发现在实现Singleton模式时,无论是C++,还是Java,或是Object Pascal,无一例外地提到了“双检测锁定”,号称是“既不影响访问速度又解决了并发冲突”,其实这一方法并不可靠。

“双检测锁定”的原理大致是,当试图获得资源时,并不进行锁定操作;如果发现资源分配,则进入互斥区,然后再判断一次,如仍未初始化,再进行初始化操作。由于修改操作在互斥区完成,因此不会有两个线程(或其它运行单位)同时修改,避免了修改的并发冲突。伪代码描述如下:

var

_Instance : instance = nil

function get_instance

if _Instance = nil then

lock;

if _Instance = nil then

_Instance = create_instance

end if

unlock;

end if

Result = _Instance;

end function

粗体字部分为互斥区,同一时刻只能有同一线程进入。而问题在于,虽然同一时刻只能有一个线程进行互斥区,但它并没有保证在某一线程进行互斥区时不允许其它线程进入非互斥区。而在互斥区中如果对_Instance的赋值不是一个原子操作,在非互斥区中对它的访问将不再可靠。

为什么这样说呢?假设我们工作在一个理想的并发环境中,编译器不会对_Instance进行寄存器优化,也不会打乱代码顺序,也没有存储器的cache的同步问题,……,现在我们模拟一下两个线程的运行情况:

线程A进入get_instance

线程A取出_Instance的值

线程A判断_Instance = nil,结果为True

线程Alock;

线程A取出_Instance的值

线程A判断_Instance = nil,结果为True

线程A调用create_instance,成功

线程A将结果保存到_Instance,第一步成功

线程A挂起

线程B进入get_instance

线程B取出_Instance的值

线程B判断_Instance = nil,结果为False(很有可能,因为_Instance已经改变)

线程B返回_Instance

线程B使用返回值

线程B……

线程B挂起

线程A苏醒

线程A将结果保存到_Instance,第二步成功

线程A解锁

线程A……

现在看一下,线程B调用get_instance得到的结果究竟是什么?不要问我,我实在不知道。关键在于线程A对_Instance进行修改初打断了,而又没有阻止线程B对_Instance进行访问,导致了在一段时间内_Instance处于无法判断的非法状态。如果对_Instance的赋值是一个原子操作则没有这个问题,但很多原因可以使它不是一个原子操作。

那么如何解决这一问题呢?上面已经提到了,导致这一问题出现有两个条件:一是对_Instance的赋值不是原子操作;二是在一个线程进入互斥区时其它线程可以进入到非互区。解决方法自然就是打破这两个条件中的任何一个。

使_Instance的赋值成为原子操作是一个很不错的选择,不过由于_Instance的类型不能随便安排,未必能够实现(不过好象JVM保证了对32位数据的访问是原子操作,不知道这一保证有多大强度),但我们可以换一个思路,就是不去判断_Instance的值,改为判断一个可以用一个原子操作进行赋值的标志(假设可以用Boolean类型):

var

_Instance : instance = nil

_Inited : Boolean = False

function get_instance

if not _Inited then

lock;

if not _Inited then

_Instance = create_instance

call AtomSet(_Inited, True)

end if

unlock;

end if

Result = _Instance;

end function

由于对_Inited标志的赋值是原子操作,它是不会被打断的,所以在前面对_Inited进行判断时,_Inited的值总是有效的。

如果找不到这种类型,还有一种变通方法:

var

_Instance : instance = nil

_Inited : Integer = 0

function get_instance

if _Inited = 0 then //如果这时_Inited不为0,即使是在对它赋值时被打断了,这时_Instance中的值也是完整有效的。

lock;

if _Instance = nil then//由于已经进入了互斥区,不会有其它线程在互斥区中挂起,这一判断是可靠的

_Instance = create_instance

_Inited = 1

end if

unlock;

end if

Result = _Instance;

end function

这样,如果一个线程对_Instance赋值的操作被中断,则_Inited = 0的判断为真,当前线程会去调用lock从而被阻塞;如果一个线程对_Inited的赋值操作被中断,那么这时_Instance中已经是合法的有效值,这时无论_Inited = 0的是否成立都不会有问题了。

上面谈到了使赋值成为原子操作的方法,而另一方法当然就是要使当一个线程处理互斥区中时,其它线程不能访问_Instance。但同时,我们必须允许在没有线程处于互斥区时,所有线程可以同时访问_Instance,如果您对并发控制有一定的经验,马上就可以想到——完全正确,使用multi_read_exclusive_write的锁机制。这样就可以保证当一个线程修改_Instance时,不能有其它线程去读_Instance。但在实现时还要当心死锁,不能这样:

var

_Instance : instance = nil

function get_instance

begin_read

if not _Inited then

begin_write

if not _Inited then

_Instance = create_instance

call AtomSet(_Inited, True)

end if

end_write

end if

Result = _Instance;

end_read

end function

如果这样实现,有可能线程AB都执行了begin_read,然后线程A执行begin_write,被阻塞——等待线程B执行end_read;而线程B也会去执行begin_write,也被阻塞——等待线程A执行end_write……

正确的实现是这样的:

var

_Instance : instance = nil

function get_instance

begin_read

if not _Inited then

end_read

begin_write

if not _Inited then

_Instance = create_instance

call AtomSet(_Inited, True)

end if

end_write

begin_read

end if

Result = _Instance;

end_read

end function

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有