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 {這個解決方案除了解決了我們前面說的重復加鎖的問題外,還對我們初始化工作做了簡化,不需要構造函數了。注意,這也是golang里面一個常見的設計模式,叫做 零初始化。
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)
}
理解一個程序如何工作很簡單,但是,作者的設計思路才是關鍵,我們可以不斷的看源代碼,看別人的實現,我們能從中學到很多知識與技巧,當遇到相同的問題的時候,我們也能解決類似的問題。
我個人覺得,作為一個天朝的程序員,不能僅僅是山寨別人的軟件,學習別人的東西。還是要能進入一個新的領域,一個未知的領域,還能有所創新。
當然,作者的設計思路我們很難得知,我們看到的只是勞動的結果,但是,我們可以這樣問自己,如果我是作者,我怎么思考這個問題,然后解決這個問題。我發現,用這樣的思維去考慮問題,有時候能給我很多的啟示。
還有五分鍾就12點了,我必須睡覺了,今天也只能先回答半個問題了。至於為什么不是一個問題,而是半個問題,請聽下回分解。