# 互斥鎖
> 互斥鎖是傳統的并發程序對共享資源進行訪問控制的主要手段。它由標準庫代碼包sync中的Mutex結構體類型代表。只有兩個公開方法
- Lock
- Unlock
類型sync.Mutex的零值表示了未被鎖定的互斥量。
~~~
var mutex sync.Mutex
mutex.Lock()
~~~
### 示例
~~~
// test for Go
//
// Copyright (c) 2015 - Batu <1235355@qq.com>
package main
import (
"fmt"
"sync"
"time"
)
func main(){
//聲明
var mutex sync.Mutex
fmt.Println("Lock the lock. (G0)")
//加鎖mutex
mutex.Lock()
fmt.Println("The lock is locked.(G0)")
for i := 1; i < 4; i++ {
go func(i int) {
fmt.Printf("Lock the lock. (G%d)\n", i)
mutex.Lock()
fmt.Printf("The lock is locked. (G%d)\n", i)
}(i)
}
//休息一會,等待打印結果
time.Sleep(time.Second)
fmt.Println("Unlock the lock. (G0)")
//解鎖mutex
mutex.Unlock()
fmt.Println("The lock is unlocked. (G0)")
//休息一會,等待打印結果
time.Sleep(time.Second)
}
~~~
打印結果:
/usr/local/go/bin/go run /Users/liuxinming/go/src/example/test/test.go
Lock the lock. (G0)
The lock is locked.(G0)
Lock the lock. (G1)
Lock the lock. (G2)
Lock the lock. (G3)
Unlock the lock. (G0)
The lock is unlocked. (G0)
The lock is locked. (G1)
建議:同一個互斥鎖的成對鎖定和解鎖操作放在同一層次的代碼塊中。
# 讀寫鎖
> 針對讀寫操作的互斥鎖,它可以分別針對讀操作和寫操作進行鎖定和解鎖操作。讀寫鎖遵循的訪問控制規則與互斥鎖有所不同。
- 它允許任意讀操作同時進行
- 同一時刻,只允許有一個寫操作進行
==============華麗分割線============
- 并且一個寫操作被進行過程中,讀操作的進行也是不被允許的
- 讀寫鎖控制下的多個寫操作之間都是互斥的
- 寫操作與讀操作之間也都是互斥的
- 多個讀操作之間卻不存在互斥關系
### 讀寫鎖由結構體類型sync.RWMutex代表
寫操作的鎖定和解鎖
* func (*RWMutex) Lock
* func (*RWMutex) Unlock
讀操作的鎖定和解鎖
* func (*RWMutex) Rlock
* func (*RWMutex) RUnlock
注意:
+ 寫解鎖在進行的時候會試圖喚醒所有因欲進行讀鎖定而被阻塞的Goroutine.
+ 讀解鎖在進行的時候只會在已無任何讀鎖定的情況下試圖喚醒一個因欲進行寫鎖定而被阻塞的Goroutine
+ 若對一個未被寫鎖定的讀寫鎖進行寫解鎖,會引起一個運行時的恐慌
+ 而對一個未被讀鎖定的讀寫鎖進行讀解鎖卻不會如此
### 鎖的完整示例
~~~
// test for Go
//
// Copyright (c) 2015 - Batu <1235355@qq.com>
//
// 創建一個文件存放數據,在同一時刻,可能會有多個Goroutine分別進行對此文件的寫操作和讀操作.
// 每一次寫操作都應該向這個文件寫入若干個字節的數據,作為一個獨立的數據塊存在,這意味著寫操作之間不能彼此干擾,寫入的內容之間也不能出現穿插和混淆的情況
// 每一次讀操作都應該從這個文件中讀取一個獨立完整的數據塊.它們讀取的數據塊不能重復,且需要按順序讀取.
// 例如: 第一個讀操作讀取了數據塊1,第二個操作就應該讀取數據塊2,第三個讀操作則應該讀取數據塊3,以此類推
// 對于這些讀操作是否可以被同時執行,不做要求. 即使同時進行,也應該保持先后順序.
package main
import (
"fmt"
"sync"
"time"
"os"
"errors"
"io"
)
//數據文件的接口類型
type DataFile interface {
// 讀取一個數據塊
Read() (rsn int64, d Data, err error)
// 寫入一個數據塊
Write(d Data) (wsn int64, err error)
// 獲取最后讀取的數據塊的序列號
Rsn() int64
// 獲取最后寫入的數據塊的序列號
Wsn() int64
// 獲取數據塊的長度
DataLen() uint32
}
//數據類型
type Data []byte
//數據文件的實現類型
type myDataFile struct {
f *os.File //文件
fmutex sync.RWMutex //被用于文件的讀寫鎖
woffset int64 // 寫操作需要用到的偏移量
roffset int64 // 讀操作需要用到的偏移量
wmutex sync.Mutex // 寫操作需要用到的互斥鎖
rmutex sync.Mutex // 讀操作需要用到的互斥鎖
dataLen uint32 //數據塊長度
}
//初始化DataFile類型值的函數,返回一個DataFile類型的值
func NewDataFile(path string, dataLen uint32) (DataFile, error){
f, err := os.OpenFile(path, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0666)
//f,err := os.Create(path)
if err != nil {
fmt.Println("Fail to find", f, "cServer start Failed")
return nil, err
}
if dataLen == 0 {
return nil, errors.New("Invalid data length!")
}
df := &myDataFile{
f : f,
dataLen:dataLen,
}
return df, nil
}
//獲取并更新讀偏移量,根據讀偏移量從文件中讀取一塊數據,把該數據塊封裝成一個Data類型值并將其作為結果值返回
func (df *myDataFile) Read() (rsn int64, d Data, err error){
// 讀取并更新讀偏移量
var offset int64
// 讀互斥鎖定
df.rmutex.Lock()
offset = df.roffset
// 更改偏移量, 當前偏移量+數據塊長度
df.roffset += int64(df.dataLen)
// 讀互斥解鎖
df.rmutex.Unlock()
//讀取一個數據塊,最后讀取的數據塊序列號
rsn = offset / int64(df.dataLen)
bytes := make([]byte, df.dataLen)
for {
//讀寫鎖:讀鎖定
df.fmutex.RLock()
_, err = df.f.ReadAt(bytes, offset)
if err != nil {
//由于進行寫操作的Goroutine比進行讀操作的Goroutine少,所以過不了多久讀偏移量roffset的值就會大于寫偏移量woffset的值
// 也就是說,讀操作很快就沒有數據塊可讀了,這種情況會讓df.f.ReadAt方法返回的第二個結果值為代表的非nil且會與io.EOF相等的值
// 因此不應該把EOF看成錯誤的邊界情況
// so 在讀操作讀完數據塊,EOF時解鎖讀操作,并繼續循環,嘗試獲取同一個數據塊,直到獲取成功為止.
if err == io.EOF {
//注意,如果在該for代碼塊被執行期間,一直讓讀寫所fmutex處于讀鎖定狀態,那么針對它的寫操作將永遠不會成功.
//切相應的Goroutine也會被一直阻塞.因為它們是互斥的.
// so 在每條return & continue 語句的前面加入一個針對該讀寫鎖的讀解鎖操作
df.fmutex.RUnlock()
//注意,出現EOF時可能是很多意外情況,如文件被刪除,文件損壞等
//這里可以考慮把邏輯提交給上層處理.
continue
}
}
break
}
d = bytes
df.fmutex.RUnlock()
return
}
func (df *myDataFile) Write(d Data) (wsn int64, err error){
//讀取并更新寫的偏移量
var offset int64
df.wmutex.Lock()
offset = df.woffset
df.woffset += int64(df.dataLen)
df.wmutex.Unlock()
//寫入一個數據塊,最后寫入數據塊的序號
wsn = offset / int64(df.dataLen)
var bytes []byte
if len(d) > int(df.dataLen){
bytes = d[0:df.dataLen]
}else{
bytes = d
}
df.fmutex.Lock()
df.fmutex.Unlock()
_, err = df.f.Write(bytes)
return
}
func (df *myDataFile) Rsn() int64{
df.rmutex.Lock()
defer df.rmutex.Unlock()
return df.roffset / int64(df.dataLen)
}
func (df *myDataFile) Wsn() int64{
df.wmutex.Lock()
defer df.wmutex.Unlock()
return df.woffset / int64(df.dataLen)
}
func (df *myDataFile) DataLen() uint32 {
return df.dataLen
}
func main(){
//簡單測試下結果
var dataFile DataFile
dataFile,_ = NewDataFile("./mutex_2015_1.dat", 10)
var d=map[int]Data{
1:[]byte("batu_test1"),
2:[]byte("batu_test2"),
3:[]byte("test1_batu"),
}
//寫入數據
for i:= 1; i < 4; i++ {
go func(i int){
wsn,_ := dataFile.Write(d[i])
fmt.Println("write i=", i,",wsn=",wsn, ",success.")
}(i)
}
//讀取數據
for i:= 1; i < 4; i++ {
go func(i int){
rsn,d,_ := dataFile.Read()
fmt.Println("Read i=", i,",rsn=",rsn,",data=",d, ",success.")
}(i)
}
time.Sleep(10 * time.Second)
}
~~~
打印結果:
> /usr/local/go/bin/go run /Users/liuxinming/go/src/example/test/test.go
write i= 3 ,wsn= 1 ,success.
write i= 1 ,wsn= 0 ,success.
Read i= 1 ,rsn= 1 ,data= [116 101 115 116 49 95 98 97 116 117] ,success.
write i= 2 ,wsn= 2 ,success.
Read i= 2 ,rsn= 0 ,data= [98 97 116 117 95 116 101 115 116 49] ,success.
Read i= 3 ,rsn= 2 ,data= [98 97 116 117 95 116 101 115 116 50] ,success.
- 前言
- golang學習(一)之安裝
- Go語言學習二:Go基礎(變量、常量、數值類型、字符串、錯誤類型)
- Go語言學習三:Go基礎(iota,array,slice,map,make,new)
- Go語言學習四:struct類型
- Ubuntu 14.04/CentOS 6.5中安裝GO LANG(GO語言)
- Mac OS 安裝golang
- Mac install Thrift
- Thrift RPC 使用指南實戰(附golang&amp;PHP代碼)
- golang net/http包使用
- 冒泡排序Bubble sort-golang
- 快速排序Quick sort - golang
- Go語言學習:Channel是什么?
- Golang的select/非緩沖的Channel實例詳解
- Golang time包的定時器/斷續器
- Golang同步:鎖的使用案例詳解
- Golang同步:條件變量和鎖組合使用
- Golang同步:原子操作使用
- Golang之bytes.buffer
- Golang之字符串格式化
- Golang之反射reflect包
- Go語言配置文件解析器,類似于Windows下的INI文件.