### 使用互斥鎖線程同步
互斥鎖是最簡單的一種鎖類型,同時也比較暴力,當一個goroutine獲得了鎖之后,其他goroutine就只能乖乖等到這個goroutine釋放該鎖。go語言使用sync.Mutex實現互斥鎖。
Mutex 是最簡單的一種鎖類型,同時也比較暴力,當一個 goroutine 獲得了 Mutex 后,其他 goroutine 就只能乖乖等到這個 goroutine 釋放該 Mutex。
參考:http://c.biancheng.net/view/107.html
參考:https://blog.csdn.net/luoye4321/article/details/82433144
```
package main
import (
"fmt"
"sync"
)
var (
// 邏輯中使用的某個變量
count int
// 與變量對應的使用互斥鎖
countGuard sync.Mutex
)
func GetCount() int {
// 鎖定
countGuard.Lock()
// 在函數退出時解除鎖定
defer countGuard.Unlock()
return count
}
func SetCount(c int) {
countGuard.Lock()
count = c
countGuard.Unlock()
}
func main() {
// 可以進行并發安全的設置
SetCount(1)
// 可以進行并發安全的獲取
fmt.Println(GetCount())
}
```
```
package main
import (
"fmt"
"sync"
"time"
)
type MutexInfo struct {
mutex sync.Mutex
infos []int
}
func (m *MutexInfo) addInfo(value int) {
m.mutex.Lock()
m.infos = append(m.infos, value)
m.mutex.Unlock()
}
func main() {
m := MutexInfo{}
for i := 0; i < 10; i++ {
go m.addInfo(i)
}
time.Sleep(time.Second * 5)
fmt.Println(m.infos)
// [0 1 2 5 3 6 7 8 9 4]
}
```
我們通過多次運行發現,輸出的結果并不總是從0到9按順序輸出,說明創建的10個goroutine并不是有序的搶占線程的執行權,也就是說這種同步并不是有序的同步,我們可以讓10個goroutine一個一個的同步執行,但是并不能安排執行次序。
運行到這里,假如我們注釋掉同步鎖的代碼為發生什么?
我們將addInfo方法修改如下:
```
func (m *MutexInfo) addInfo(value int) {
//m.mutex.Lock()
m.infos = append(m.infos, value)
//m.mutex.Unlock()
}
```
運行代碼,輸出:[1 0 2]
結果是不是出乎意料?為什么寫了10個輸入,只有3個值輸入成功?這時候我們不得不解釋線程的另一個概念,那就是線程安全。
我們先看下go語言中slice的append過程,使用append添加一個元素時,可能會有兩步來完成:先獲取當前切片數組的容量,比如當前容量是2,然后在新的存儲區開辟一塊新的存儲單元,容量為2+1,并將原來的值和新的值存入新的存儲單元。在沒有同步鎖的情況下,如果兩個線程同時執行添加元素的操作,這時候可能只有一個被寫入成功。這種情況就是非線程安全,相比之下,如果同時對一個int類型數據進行操作,就不會出現這種非線程安全的情況。
### 線程同步(讀寫互斥鎖)
go語言提供了另一種更加友好的線程同步的方式:sync.RWMutex。相對于互斥鎖的簡單暴力,讀寫鎖更加人性化,是經典的單寫多讀模式。在讀鎖占用的情況下,會阻止寫,但不阻止讀,也就是多個goroutine可同時獲取讀鎖,而寫鎖會阻止其他線程的讀寫操作。
RWMutex 相對友好些,是經典的單寫多讀模型。在讀鎖占用的情況下,會阻止寫,但不阻止讀,也就是多個 goroutine 可同時獲取讀鎖(調用 RLock() 方法;而寫鎖(調用 Lock() 方法)會阻止任何其他 goroutine(無論讀和寫)進來,整個鎖相當于由該 goroutine 獨占。從 RWMutex 的實現看,RWMutex 類型其實組合了 Mutex
```
type RWMutex struct {
? ? w Mutex
? ? writerSem uint32
? ? readerSem uint32
? ? readerCount int32
? ? readerWait int32
}
```
```
package main
import (
"fmt"
"sync"
"time"
)
// 創建一個結構體
type MutexInfo struct {
mutex sync.Mutex
infos []int
}
//執行讀的函數
func (m *MutexInfo) addInfo(value int) {
// 加鎖
m.mutex.Lock()
// 結束時 釋放鎖 去鎖操作
defer m.mutex.Unlock()
fmt.Println("開始讀", value)
fmt.Println("結束讀", value)
}
// 執行寫的函數
func (m *MutexInfo) readInfo(value int) {
// 加鎖
m.mutex.Lock()
// 釋放
defer m.mutex.Unlock()
fmt.Println("開始寫", value)
m.infos = append(m.infos, value)
fmt.Println("結束寫", value)
}
func main() {
//實例化結構體
m := MutexInfo{}
// 創建10個線程
for i := 0; i < 10; i++ {
go m.addInfo(i)
go m.readInfo(i)
}
time.Sleep(time.Second * 5)
// 輸出 結構體接收數據 infos
fmt.Println(m.infos)
}
```
開始讀 1
結束讀 1
...
結束讀 8
開始讀 9
結束讀 9
[0 7 1 2 3 4 5 6 8 9]
從結果我們可以看出,開始的時候讀線程占用讀鎖,并且多個線程可以同時開始讀操作,但是寫操作只能單個進行。
### 使用條件變量實現線程同步
go語言提供了條件變量sync.Cond,sync.Cond方法如下:
Wait,Signal,Broadcast。
Wait添加一個計數,也就是添加一個阻塞的goroutine。
Signal解除一個goroutine的阻塞,計數減一。
Broadcast接觸所有wait goroutine的阻塞。
```
package main
import (
"fmt"
"sync"
"time"
)
func printIntValue(value int, cond *sync.Cond) {
cond.L.Lock()
if value < 5 {
//value小于5時,進入等待狀態
cond.Wait()
}
//大于5的正常輸出
fmt.Println(value)
cond.L.Unlock()
}
func main() {
//條件等待
mutex := sync.Mutex{}
//使用鎖創建一個條件等待
cond := sync.NewCond(&mutex)
for i := 0; i < 10; i++ {
go printIntValue(i, cond)
}
time.Sleep(time.Second * 1)
cond.Signal() //解除一個阻塞
time.Sleep(time.Second * 1)
cond.Broadcast() //解除全部阻塞
time.Sleep(time.Second * 1)
}
```
運行后先輸出滿足條件的值:5 6 7 8 9
解除一個阻塞,輸出0,解除全部阻塞,輸出1 2 3 4
go語言多線程支持全局唯一性操作,即一個只允許goruntine調用一次,重復調用無效。
- 安裝開發環境
- 安裝開發環境
- 安裝詳細教程
- 引入包
- Go語言基礎
- 基本變量與數據類型
- 變量
- 數據類型
- 指針
- 字符串
- 代碼總結
- 常量與運算符
- 常量
- 運算符
- 流程控制
- if判斷
- for循環
- switch分支
- goto跳轉
- 斐波那契數列
- Go語言內置容器
- 數組
- 切片
- 映射
- 函數
- 函數(上)
- 函數(中)
- 函數(下)
- 小節
- 包管理
- 結構體
- 結構體(上)
- 結構體(中)
- 結構體(下)
- 小節
- 錯誤處理
- 錯誤處理
- 宕機
- 錯誤應用
- 小節
- 文件操作
- 獲取目錄
- 創建和刪除目錄
- 文件基本操作(上)
- 文件基本操作(中)
- 文件基本操作(下)
- 處理JSON文件
- 接口與類型
- 接口的創建與實現
- 接口賦值
- 接口嵌入
- 空接口
- 類型斷言(1)
- 類型斷言(2)
- 小節
- 并發與通道
- goroutine協程
- runtime包
- 通道channel
- 單向通道channel
- select
- 線程同步
- 多線程的深入學習
- http編程
- http簡介
- Client和Request
- get請求
- post請求
- 模塊函數方法
- 模塊
- fmt庫,模塊
- 項目練習
- 爬蟲:高三網
- 爬蟲:快代理
- 爬蟲:快代理2
- 多線程:通道思路
- 多線程爬蟲:快代理