go sync.Mutex 设计思想与演化过程 (一)
go语言在云计算时代将会如日中天,还抱着.NET不放的人将会被淘汰。学习go语言和.NET完全不一样,它有非常简单的runtime 和 类库。最好的办法就是将整个源代码读一遍,这是我见过最简洁的系统类库。读了之后,你会真正体会到C#的面向对象的表达方式是有问题的,继承并不是必要的东西。相同的问题,在go中有更加简单的表达。
go runtime 没有提供任何的锁,只是提供了一个PV操作原语。独占锁,条件锁 都是基于这个原语实现的。如果你学习了go,那就就知道如何在windows下高效的方式实现条件锁定(windows没有自带的条件锁)。
我想阅读源代码,不能仅仅只看到实现了什么,还要看到作者的设计思路,还有如果你作为作者,如何实现。这些才是真正有用的东西,知识永远学不完,我们要锻炼我们的思维。
要写这篇文章的背景就忽略吧,我已经很久没有写博客了,主要原因是我基本上看不到能让我有所帮助的博客,更多的是我认为我也写不出能对别人有所帮助的文章。为了写这篇文章,我还是花了挺多的心思收集历史资料, 论坛讨论,并去golang-nuts 上咨询了一些问题。希望对大家有所帮助。
一. sync.Mutex 是什么?
Mutex是一种独占锁,一般操作系统都会提供这种锁。但是,操作系统的锁是针对线程的,golang里面没有线程的概念,这样操作系统的锁就用不上了。所以,你看go语言的runtime,就会发现,实际上这是一个“操作系统”。如果Mutex还不知道的话,我建议看下面的文章,其中第一篇必看。
百度百科 mutex http://baike.baidu.com/view/1461738.htm?fromId=1889552&redirected=seachword
信号量:http://swtch.com/semaphore.pdf
还可以读一下百度百科 pv 操作:http://baike.baidu.com/view/703687.htm
二. golang 最新版本的 sync.Mutex
你可以大致扫描一下最新版本的实现,如果你第一眼就看的很懂了,每步的操作?为什么这样操作?有没有更加合理的操作?那恭喜你,你的水平已经超过google实现 sync.Mutex 的程序员了,甚至是大部分的程序员,因为这个程序历经几年的演化,才到了今天的样子,你第一眼就能看的如此透彻,那真的是很了不起。下面的章节是为没有看懂的人准备的。
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // Package sync provides basic synchronization primitives such as mutual
// exclusion locks. Other than the Once and WaitGroup types, most are intended
// for use by low-level library routines. Higher-level synchronization is
// better done via channels and communication.
//
// Values containing the types defined in this package should not be copied.
package sync import (
"sync/atomic"
"unsafe"
) // A Mutex is a mutual exclusion lock.
// Mutexes can be created as part of other structures;
// the zero value for a Mutex is an unlocked mutex.
type Mutex struct {
state int32
sema uint32
} // A Locker represents an object that can be locked and unlocked.
type Locker interface {
Lock()
Unlock()
} const (
mutexLocked = 1 << iota // mutex is locked
mutexWoken
mutexWaiterShift = iota
) // Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock() {
// Fast path: grab unlocked mutex.
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if raceenabled {
raceAcquire(unsafe.Pointer(m))
}
return
} awoke := false
for {
old := m.state
new := old | mutexLocked
if old&mutexLocked != 0 {
new = old + 1<<mutexWaiterShift
}
if awoke {
// The goroutine has been woken from sleep,
// so we need to reset the flag in either case.
new &^= mutexWoken
}
if atomic.CompareAndSwapInt32(&m.state, old, new) {
if old&mutexLocked == 0 {
break
}
runtime_Semacquire(&m.sema)
awoke = true
}
} if raceenabled {
raceAcquire(unsafe.Pointer(m))
}
} // Unlock unlocks m.
// It is a run-time error if m is not locked on entry to Unlock.
//
// A locked Mutex is not associated with a particular goroutine.
// It is allowed for one goroutine to lock a Mutex and then
// arrange for another goroutine to unlock it.
func (m *Mutex) Unlock() {
if raceenabled {
_ = m.state
raceRelease(unsafe.Pointer(m))
} // Fast path: drop lock bit.
new := atomic.AddInt32(&m.state, -mutexLocked)
if (new+mutexLocked)&mutexLocked == 0 {
panic("sync: unlock of unlocked mutex")
} old := new
for {
// If there are no waiters or a goroutine has already
// been woken or grabbed the lock, no need to wake anyone.
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken) != 0 {
return
}
// Grab the right to wake someone.
new = (old - 1<<mutexWaiterShift) | mutexWoken
if atomic.CompareAndSwapInt32(&m.state, old, new) {
runtime_Semrelease(&m.sema)
return
}
old = m.state
}
}
三. 有没有更加简洁的实现方法?
有点操作系统知识的都知道,独占锁是一种特殊的PV 操作,就 0 – 1 PV操作。那我想,如果不考虑任何性能问题的话,用信号量应该就可以这样实现Mutex:
type Mutex struct {
sema uint32
}
func NewMutex() *Mutex {
var mu Mutex
mu.sema = 1
return &mu
}
func (m *Mutex) Lock() {
runtime_Semacquire(&m.sema)
}
func (m *Mutex2) Unlock() {
runtime_Semrelease(&m.sema)
}
当然,这个实现有点不符合要求。如果有个家伙不那么靠谱,加锁了一次,但是解锁了两次。第二次解锁的时候,应该报出一个错误,而不是让错误隐藏。于是乎,我们想到用一个变量表示加锁的次数。这样就可以判断有没有多次解锁。于是乎,我就想到了下面的解决方案:
type Mutex struct {
key int32
sema uint32
}
func (m *Mutex) Lock() {
if atomic.AddInt32(&m.key, 1) == 1 {
// changed from 0 to 1; we hold lock
return
}
runtime_Semacquire(&m.sema)
}
func (m *Mutex) Unlock() {
switch v := atomic.AddInt32(&m.key, -1); {
case v == 0:
// changed from 1 to 0; no contention
return
case v == -1:
// changed from 0 to -1: wasn't locked
// (or there are 4 billion goroutines waiting)
panic("sync: unlock of unlocked mutex")
}
runtime_Semrelease(&m.sema)
}
这个解决方案除了解决了我们前面说的重复加锁的问题外,还对我们初始化工作做了简化,不需要构造函数了。注意,这也是golang里面一个常见的设计模式,叫做 零初始化。


理解一个程序如何工作很简单,但是,作者的设计思路才是关键,我们可以不断的看源代码,看别人的实现,我们能从中学到很多知识与技巧,当遇到相同的问题的时候,我们也能解决类似的问题。
我个人觉得,作为一个天朝的程序员,不能仅仅是山寨别人的软件,学习别人的东西。还是要能进入一个新的领域,一个未知的领域,还能有所创新。
当然,作者的设计思路我们很难得知,我们看到的只是劳动的结果,但是,我们可以这样问自己,如果我是作者,我怎么思考这个问题,然后解决这个问题。我发现,用这样的思维去考虑问题,有时候能给我很多的启示。
还有五分钟就12点了,我必须睡觉了,今天也只能先回答半个问题了。至于为什么不是一个问题,而是半个问题,请听下回分解。
go sync.Mutex 设计思想与演化过程 (一)的更多相关文章
- 从一般分布式设计看HDFS设计思想与架构
要想深入学习HDFS就要先了解其设计思想和架构,这样才能继续深入使用HDFS或者深入研究源代码.懂得了"所以然"才能在实际使用中灵活运用.快速解决遇到的问题.下面这篇博文我们就先 ...
- 《深入理解Android内核设计思想》
<深入理解Android内核设计思想> 基本信息 作者: 林学森 出版社:人民邮电出版社 ISBN:9787115348418 上架时间:2014-4-25 出版日期:2014 年5月 开 ...
- golang 中 sync.Mutex 的实现
mutex 的实现思想 mutex 主要有两个 method: Lock() 和 Unlock() Lock() 可以通过一个 CAS 操作来实现 func (m *Mutex) Lock() { f ...
- 09A-独立按键消抖实验01——小梅哥FPGA设计思想与验证方法视频教程配套文档
芯航线--普利斯队长精心奉献 实验目的: 1.复习状态机的设计思想并以此为基础实现按键消抖 2.单bit异步信号同步化以及边沿检测 3.在激励文件中学会使用随机数发生函数$random 4.仿真模 ...
- FPGA重要设计思想
FPGA重要设计思想 1.速度和面积互换原则.以面积换速度可以实现很高的数据吞吐率,其实串/并转换.就是一种以面积换速度的思想 2.乒乓操作. 3.串/并转换的思想. 高速数据处理的重要技巧之一. ...
- Volley设计思想和流程分析
本文是对Volley思路的整体整理,并不是Volley教程,建议有Volley使用经验,但是对Volley整体不是很清楚的同学阅读. 我认为,弄清整体的流程很重要,以避免一叶障目不见泰山的囧境,而对于 ...
- MapReduce原理与设计思想
简单解释 MapReduce 算法 一个有趣的例子 你想数出一摞牌中有多少张黑桃.直观方式是一张一张检查并且数出有多少张是黑桃? MapReduce方法则是: 给在座的所有玩家中分配这摞牌 让每个玩家 ...
- CEPH浅析”系列之三——CEPH的设计思想
Ceph针对的目标应用场景 理解Ceph的设计思想,首先还是要了解Sage设计Ceph时所针对的目标应用场景,换言之,"做这东西的目的是啥?" 事实上,Ceph最初针对的目标应用场 ...
- Delphi 包的设计思想及它与PAS、BPL、DCU、DLL、OXC的关系。
DCP ,BPL分别是什么文件,起什么作用?你在DELPHI中建立一个package然后保存一下,看看. bpl和Dll比较相似.只是BPL是BORLAND自己弄出来的东西!!!调用也和调用DLL相似 ...
随机推荐
- 介绍开源的.net通信框架NetworkComms框架 源码分析(十六 ) ConnectionStatic
原文网址: http://www.cnblogs.com/csdev Networkcomms 是一款C# 语言编写的TCP/UDP通信框架 作者是英国人 以前是收费的 目前作者已经开源 许可是 ...
- 火狐浏览器+Firebug+FirePath测试Xpath
前言 抓取网页数据时使用HtmlAgilityPack分析,需要通过xpath定位页面元素.如果有个xpath的生成和验证工具就事半功倍了,火狐浏览器插件FirePath配合Firebug就能完美实现 ...
- C#读取XML文件的基类实现
刚到新单位,学习他们的源代码,代码里读写系统配置文件的XML代码比较老套,直接写在一个系统配置类里,没有进行类的拆分,造成类很庞大,同时,操作XML的读写操作都是使用SetAttribute和node ...
- LeetCode121:Best Time to Buy and Sell Stock
题目: Say you have an array for which the ith element is the price of a given stock on day i. If you w ...
- 【Linux_Fedora_应用系列】_3_如何利用Smplayer播放WMV格式的文件
在上一篇我们成功安装了视频播放器,并且成功安装里解码器[Linux_Fedora_应用系列]_2_如何安装视频播放器和视频文件解码 安装完的Smplayer的GUI的界面程序,可以播放FLV.AVI. ...
- redis事务详解
mysql中也存在事务的概念.其实事务的定义是一样的.一组操作的集合,作为一个整体,要么全执行,要么全不执行. redis设置事务三步骤: 开始事务 :multi 操作加入事务队列 执行事务 :exe ...
- static关键字详解
首先,要了解一下这些东西的存放位置 堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息.(class的目的是得到操作指令) 2.jvm只有一个堆区(heap)被所有线程共享,堆 ...
- shiro realm 注解失败问题解决过程
做为一名在.net混了八九年的老兵油子,转战java时间并不长,刚开始做项目完全是凭借对C#的认识来做,虽然遇到一些问题,但实际结果显示C#在语言上和java还是有很大相似度,而且微软的MVC与Spr ...
- pywebsocket的搭建
Python可以搭建pywebsocket(Web服务器,python websocket),搭建pywebsocket必须要已经安装了python,点我查看python的下载与安装.在这篇Blog中 ...
- 使用XmlHelper添加节点C#代码
接着上一篇:http://keleyi.com/a/bjac/ttssua0f.htm在前篇文章中,给出了C# XML文件操作类XmlHelper的代码,以及使用该类的一个例子,即使用XmlHelpe ...