无锁编程 - Double-checked Locking
Double-checked Locking,严格意义上来讲不属于无锁范畴,无论什么时候当临界区中的代码仅仅需要加锁一次,同时当其获取锁的时候必须是线程安全的,此时就可以利用 Double-checked Locking 模式来减少锁竞争和加锁载荷。目前Double-checkedLocking已经广泛应用于单例 (Singleton)模式中。
Double-checked Locking有以下特点:
Double-checked Locking模式是Singleton的多线程版本。 Double-checked Locking模式依旧会使用锁——临界区锁定,不要以为可以避免使用锁。 Double-checked Locking解决的问题是:当多个线程存在访问临界区企图时,保证了临界区只需要访问一次。
以Singleton为例,为了防止多次分配,通常Singleton的实现方式是:
// 实现1
Class singleton
{
singleton* get_instance()
{
lock();
if (instance == 0)
{
instance = new singleton;
}
unlock();
return instance;
}
}
这里存在的问题是:无论是否已经初始化都要加锁,增加了负荷,已经没有所谓的并发性能了。
要增加并发性能,可以先判断是否已经分配,在没分配的情况下才加锁,也许你想要改成下面这个样子:
// 实现2
Class singleton
{
singleton* get_instance()
{
if (instance == 0)
{
lock();
instance = new singleton;
unlock();
}
return instance;
}
}
这里存在的问题是:不能保证临界区只初始化一次,没能实现singleton的基本功能。
// 实现3 - Double-checkedLocking
Class singleton
{
singleton* get_instance()
{
if (instance == 0)
{
lock();
if (instance == 0 )
{
instance = new singleton;
}
unlock();
}
return instance;
}
}
严格的说,Double-checked locking不属于无锁编程的范畴,但由原来的每次加锁访问到大多数情况下无须加锁,就是一个巨大的进步。
golang 设计模式之singleton:
golang
1.5版本之后默认设置GOMAXPROCS
值为当前计算机真实核心数,使得goroutines
从默认的单线程内并发执行
变成了默认的(真实核心数支持的)的多线程内并行
执行。多线程并行执行goroutines
需要考虑并行执行过程中引入的线程安全问题
。
单线程singleton模型
单例模式定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。先摘取一个在实际工作项目中碰到的singleton模型代码:
1 |
package singleton type singleton struct { |
上面的singleton
设计代码在见过的几个golang
项目中都是这么写的,如果这段代码放在golang
1.5版本之前默认的单线程场景下运行显然是没什么问题的。golang
1.5版本之后默认是启动多核并行执行goroutines
的,如果上述代码放在golang
1.5版本之后,如果程序开启n个goroutines
初始化一个singleton
对象, 显然会初始化成功最少一个最多n个singleton
对象,从而会存在潜在的多个单例实例对象了,也就不可能保证这个singleton
对象全局唯一性了,那后继采用singleton
对象进行全局唯一性操作时势必会造成数据不一致的问题。如果场景中但个goroutines
执行时间短的话,会使得调试更难。
采用互斥锁机制
面对上述线程安全问题,一般会考虑到用锁机制(Mutex
)来解决因线程安全引入的数据不一致问题,采用锁机制如:
1 |
var mu Sync.Mutex func GetInstance() *singleton { |
上述代码可以看到,引入锁机制Sync.Mutex
后,能够保证多线程并行执行goroutines
创建的singleton
实例对象是唯一的,但是当这个singleton
实例对象被初始化创建之后,再次并行来创建singleton
实例对象时,其实已经不再需要锁了,因为已经存在了一个创建好的singleton
实例对象,所以直接返回即可;但是因为锁机制的存在,使得再次创建singleton
实例对象时,还是需要先获取锁,然后在判断处理,多线程执行中这种锁竞争使得多线程的并行执行变成了多线程的串行执行,这显然会使程序丧失并行执行带来的性能提升。在一个高度并行的程序中,这样显示会是抑制程序性能提升的一个瓶颈。
采用双重检查锁机制
在C++
等编程语言中,为了同时保证最小锁和线程安全通常采用的方法是双重检查锁(Check-Lock-Check)
机制,也表述为DCL(Double Check Lock)
。双重检查锁
机制的伪代码一般是下面的这种形式:
1 |
if check() { |
其实对这个singleton
实例对象来说,只有在第一次创建实例的时候才需要同步,所以为了减少同步,先check一下,判断singleton
实例对象是否为空,如果为空,表示是第一使用这个singleton
实例对象,那就锁住它,new一个singleton
实例,下次另一个线程来GetInstance
的时候,看到这个singleton
实例对象不为空,就表示已经创建过一个实例了,那就可以直接得到这个实例,避免再次锁。这是第一个 check的作用。
第二个check是解决锁竞争情况下的问题,假设现在两个线程来请求GetInstance
,A、B线程同时发现singleton
实例对象为空,因为我们在第一次check方法上没有加锁,然后A线程率先获得锁,进入同步代码块,new了一个singleton
实例对象,之后释放锁,接着B线程获得了这个锁,发现singleton
实例对象已经被创建了,就直接释放锁,退出同步代码块。所以这就是Check-Lock-Check
; 将上面的singleton
实例用Check-Lock-Check
机制实现如:
1 |
func GetInstance() *singleton { |
通过上面的Check-Lock-Check
机制,的确可以解决锁竟争的问题,但是这种方法不管是否singleton
实例对象是否已创建,每次都要执行两次check才是一个完整的判断,那有没有方法使得只要一次check就可以完成对singleton
实例对象是否存在的检查呢? 有!通过golang
的sync/atomic
包提供的原子性操作可以更高效的完成这个检查,改进代码如:
1 |
import "sync" |
改进之后的代码通过设置一个标志操作,使得singleton
实例对象创建之后,直接通过原子操作读取标志字段的值判断返回已经存在的实例,连锁操作及其后面的代码都略过了。
采用atomic进一步简化
上面通过Check-Lock-Check
机制改进之后似乎没有什么可做的了,先不急,来看看golang
原生标准包sync
包中对Once
实现的源码:
1 |
// Once is an object that will perform exactly one action. |
可以看到我们之前其实是借鉴了golang
原生标准包sync
中对Once
实现对源码,那既然标准包中已经实现了这个Check-Lock-Check
机制,那我们直接调用sync
包提供once.Do()
方法对某个方法只进行一次性调用:
1 |
once.Do(func() { |
那么下面是根据sync
包提供的sync.Once
改进的获取singleton
实例对象最终优化版本:
1 |
package singleton import ( |
因此使用sync
包提供的sync.Once
实现获取singleton
实例对象可以说是最安全有效又简洁的方法。
无锁编程 - Double-checked Locking的更多相关文章
- Java中的双重检查锁(double checked locking)
最初的代码 在最近的项目中,写出了这样的一段代码 private static SomeClass instance; public SomeClass getInstance() { if (nul ...
- 无锁编程(一) - Double-checked Locking
Double-checked Locking,严格意义上来讲不属于无锁范畴,无论什么时候当临界区中的代码仅仅需要加锁一次,同时当其获取锁的时候必须是线程安全的,此时就可以利用 Double-che ...
- [转]透过 Linux 内核看无锁编程
非阻塞型同步 (Non-blocking Synchronization) 简介 如何正确有效的保护共享数据是编写并行程序必须面临的一个难题,通常的手段就是同步.同步可分为阻塞型同步(Blocking ...
- C++11原子操作与无锁编程(转)
不讲语言特性,只从工程角度出发,个人觉得C++标准委员会在C++11中对多线程库的引入是有史以来做得最人道的一件事:今天我将就C++11多线程中的atomic原子操作展开讨论:比较互斥锁,自旋锁(sp ...
- C++性能榨汁机之无锁编程
C++性能榨汁机之无锁编程 来源 http://irootlee.com/juicer_lock_free/ 前言 私以为个人的技术水平应该是一个螺旋式上升的过程:先从书本去了解一个大概,然后在实践中 ...
- 无锁编程以及CAS
无锁编程 / lock-free / 非阻塞同步 无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Sy ...
- 4.锁--无锁编程以及CAS
无锁编程以及CAS 无锁编程 / lock-free / 非堵塞同步 无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被堵塞的情况下实现变量的同步,所以也叫非堵塞同步(Non-b ...
- 海量并发的无锁编程 (lock free programming)
最近在做在线架构的实现,在线架构和离线架构近线架构最大的区别是服务质量(SLA,Service Level Agreement,SLA 99.99代表10K的请求最多一次失败或者超时)和延时.而离线架 ...
- 【Java并发编程】2、无锁编程:lock-free原理;CAS;ABA问题
转自:http://blog.csdn.net/kangroger/article/details/47867269 定义 无锁编程是指在不使用锁的情况下,在多线程环境下实现多变量的同步.即在没有线程 ...
随机推荐
- C# using 的用法
Ø 前言 说起 C# using 语句,想必大家都不陌生,它是 C# 中关键字之一.我们基本每天写代码都会使用到,其实也非常简单. 1. 首先,说说 using 有哪些用途 1) 用于引用其 ...
- luogu 2569 股票交易 单调队列dp
注意转移方程 分1.凭空买 2.不买不卖 3.在原来基础上买 4.在原来基础上卖 四种情况 head=1,tail=0;再判断一下head<=tail也可以 #include<bits/s ...
- Webstorm添加新建.vue文件功能并支持高亮vue语法和es6语法
转载:https://blog.csdn.net/qq_33008701/article/details/56486893 Webstorm 添加新建.vue文件功能并支持高亮vue语法和es6语法 ...
- 2017CCPC秦皇岛 A题Balloon Robot&&ZOJ3981【模拟】
题意: 一个机器人在长为M的圆形轨道上送气球,当机器人到达M号点的时候下一站会回到1号点,且全程不会停止运动.现在在长为M的轨道上有N个队伍,队伍会在某个时间做需要一个气球,机器人需要送过去.一共有P ...
- MacOS安装kafka可视化工具Kafka Tool
1 下载地址 http://www.kafkatool.com/download.html 2 下载dmg包,选择对应版本,我的kafka是2.1的版本,所以选择了Kafka Tool 2.0.4 3 ...
- 【blog】Markdown的css样式推荐
参考博客 分享一款Markdown的css样式:https://www.cnblogs.com/zhangjk1993/p/5442676.html 美化Markdown输出的HTML文档:http: ...
- Css/Js推荐类库
animate.css https://daneden.github.io/animate.css WOW.js http://mynameismatthieu.com/WOW owl.carouse ...
- react-踩坑记录——页面底部多出一倍高度的空白
挂载slider组件后页面底部多出一倍高度的空白,如下: slider组件内容⬇️: class Slider extends Component{ constructor(){ super(); } ...
- no plugin found for prefix 'tomcat 7' in the current project
使用maven build编译出错 “no plugin found for prefix 'tomcat 7' in the current project..........” 参照下面方法 ht ...
- html超文本标记语言基础一
1,基本格式 <!DOCTYPE html> //声明为 HTML5 文档 <html> <head> <meta charset="utf-8&q ...