用喜歡和舒服的方式在Golang中使用鎖、使用channel自定義鎖
眾所周知,我們能使用Golang輕鬆編寫併發程式。Golang利用goroutine,讓我們編寫併發程式變得容易。併發程式中重要的問題之一就是如何正確的處理“競爭資源”或“共享資源”。Golang為我們提供了鎖的機制。這篇文章,就簡單介紹Golang中鎖的使用方法。並且進行錯誤的使用方法和正確的使用方法的程式碼示例對比。文章的所以程式碼示例在:https://github.com/pathbox/learning-go/tree/master/src/lock。
我們看第一個栗子:
package main
import (
"fmt"
"sync"
)
type Counter struct {
Value int
}
var wg sync.WaitGroup
var mutex sync.Mutex // 宣告瞭一個全域性鎖
func main() {
wg.Add(1000)
counter := &Counter{Value: 0}
for i := 0; i < 1000; i++ {
go Count(counter, mutex)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}
func Count(counter *Counter, mutex sync.Mutex) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}
/*
輸出結果:
Count Value: 982
*/
這裡宣告瞭一個全域性鎖 sync.Mutex,然後將這個全域性鎖以引數的方式代入到方法中,這樣並沒有真正起到加鎖的作用。
正確的方式是:
package main
import (
"fmt"
"sync"
)
type Counter struct {
Value int
}
var wg sync.WaitGroup
var mutex sync.Mutex // 宣告瞭一個全域性鎖
func main() {
wg.Add(1000)
counter := &Counter{Value: 0}
for i := 0; i < 1000; i++ {
go Count(counter)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}
func Count(counter *Counter) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}
/*
輸出結果:
Count Value: 1000
*/
宣告瞭一個全域性鎖後,其作用範圍是全域性。直接使用,而不是將其作為引數傳遞到方法中。
下一個栗子
package main
import (
"fmt"
"sync"
)
type Counter struct {
Value int
}
var wg sync.WaitGroup
func main() {
var mutex sync.Mutex // 宣告瞭一個非全域性鎖
wg.Add(1000)
counter := &Counter{Value: 0}
for i := 0; i < 1000; i++ {
go Count(counter, mutex)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}
func Count(counter *Counter, mutex sync.Mutex) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}
/*
輸出結果:
Count Value: 954
*/
上面栗子中,宣告的不是全域性鎖。然後將這個鎖作為引數傳入到Count()方法中,這樣並沒有真正起到加鎖的作用。
正確的方式:
package main
import (
"fmt"
"sync"
)
type Counter struct {
Value int
}
var wg sync.WaitGroup
func main() {
mutex := &sync.Mutex{} // 定義了一個鎖 mutex,賦值給mutex
wg.Add(1000)
counter := &Counter{Value: 0}
for i := 0; i < 1000; i++ {
go Count(counter, mutex)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}
func Count(counter *Counter, mutex *sync.Mutex) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}
/*
輸出結果:
Count Value: 1000
*/
這次通過 mutex := &sync.Mutex{},定義了mutex,然後作為引數傳遞到方法中,正確實現了加鎖功能。
簡單的說,在全域性宣告全域性鎖,之後這個全域性鎖就能在程式碼中的作用域範圍內都能使用了。但是,也許你需要的不是全域性鎖。這和鎖的粒度有關。 所以,你可以宣告一個鎖,在其作用域範圍內使用,並且這個作用域範圍是有併發執行的,別將鎖當成引數傳遞。如果,需要將鎖當成引數傳遞,那麼你傳的不是一個鎖的宣告,而是這個鎖的指標。
下面,我們討論一種更好的使用方式。通過閱讀過很多”牛人“寫的Go的程式或原始碼庫,在鎖的使用中。常常將鎖放入對應的 struct 中定義,我覺得這是一種不錯的方法。
package main
import (
"fmt"
"sync"
)
type Counter struct {
Value int
sync.Mutex
}
var wg sync.WaitGroup
func main() {
wg.Add(1000)
counter := &Counter{Value: 0}
for i := 0; i < 1000; i++ {
go Count(counter)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}
func Count(counter *Counter) {
counter.Lock()
defer counter.Unlock()
counter.Value++
wg.Done()
}
/*
輸出結果:
Count Value: 1000
*/
這樣,我們宣告的不是全域性鎖,並且這個需要加鎖的競爭資源也正是 struct Counter 本身的Value屬性,反映了這個鎖的粒度。我覺得這是一種很舒服的使用方式(暫不知道這種方式會帶來什麼負面影響,如果有踩過坑的朋友,歡迎聊一聊這個坑),當然,如果你需要全域性鎖,那麼請定義全域性鎖。
還可以有更多的使用方式:
// 1.
type Counter struct {
Value int
Mutex sync.Mutex
}
counter := &Counter{Value: 0}
counter.Mutex.Lock()
defer counter.Mutex.Unlock()
//2.
type Counter struct {
Value int
Mutex *sync.Mutex
}
counter := &Counter{Value: 0, Mutex: &sync.Mutex{}}
counter.Mutex.Lock()
defer counter.Mutex.Unlock()
Choose the way you like~
接下來,我們自己嘗試建立一個互斥鎖。
簡單的說,簡單的互斥鎖鎖的原理是:一個執行緒(程式)拿到了這個互斥鎖,在這個時刻,只有這個執行緒(程式)能夠進行互斥鎖鎖的範圍中的"共享資源"的操作,主要是寫操作。我們這裡不討論讀鎖的實現。鎖的種類很多,有不同的實現場景和功能。這裡我們討論的是最簡單的互斥鎖。
我們能夠利用Golang 的channel所具有特性,建立一個簡單的互斥鎖。
/locker/locker.go
package locker
// Mutext struct
type Mutex struct {
lock chan struct{}
}
// 建立一個互斥鎖
func NewMutex() *Mutex {
return &Mutex{lock: make(chan struct{}, 1)}
}
// 鎖操作
func (m *Mutex) Lock() {
m.lock <- struct{}{}
}
// 解鎖操作
func (m *Mutex) Unlock() {
<-m.lock
}
main.go
package main
import (
"./locker"
"fmt"
"time"
)
type record struct {
lock *locker.Mutex
lock_count int
no_lock_count int
}
func newRecord() *record {
return &record{
lock: locker.NewMutex(),
lock_count: 0,
no_lock_count: 0,
}
}
func main() {
r := newRecord()
for i := 0; i < 1000; i++ {
go CountWithoutLock(r)
go CountWithLock(r)
}
time.Sleep(2 * time.Second)
fmt.Println("Record no_lock_count: ", r.no_lock_count)
fmt.Println("Record lock_count: ", r.lock_count)
}
func CountWithLock(r *record) {
r.lock.Lock()
defer r.lock.Unlock()
r.lock_count++
}
func CountWithoutLock(r *record) {
r.no_lock_count++
}
/* 輸出結果
Record no_lock_count: 995
Record lock_count: 1000
*/
locker 就是通過使用channel的讀操作和寫操作會互相阻塞等待的這個同步性質。 可以簡單的理解為,channel中傳遞的就是互斥鎖。一個執行緒(程式)申請了一個互斥鎖(struct{}{}),將這個互斥鎖存放在channel中, 其他執行緒(程式)就沒法申請互斥鎖放入channel,而處於阻塞狀態,等待channel恢復空閒空間。該執行緒(程式)進行操作”共享資源“,然後釋放這個互斥鎖(從channel中取走),channel這時候恢復了空閒的空間,其他執行緒(程式) 就能申請互斥鎖並且放入channel。這樣,在某一時刻,只會有一個執行緒(程式)擁有互斥鎖,在操作"共享資源"。
相關文章
- golang 中 channel 的詳細使用、使用注意事項及死鎖分析Golang
- 樂觀鎖和悲觀鎖在kubernetes中的應用
- Mac使用技巧_蘋果鎖屏介面如何自定義鎖屏訊息?Mac蘋果
- 【go】golang中鎖的用法-互斥鎖Golang
- Golang的分散式鎖元件,支援Reids,Pgsql或自定義驅動Golang分散式元件SQL
- ReentrantReadWriteLock讀寫鎖及其在 RxCache 中的使用
- 實現宣告式鎖,支援分散式鎖自定義鎖、SpEL和結合事務分散式
- 防手機鎖屏解鎖自定義ViewView
- 在 Linux 中鎖定和解鎖使用者帳戶的三種方法Linux
- golang開發:channel使用Golang
- AQS 自定義同步鎖,挺難的!AQS
- Redis在.net中的使用(6)Redis併發鎖Redis
- 在 Golang 中使用 Go 關鍵字和 Channel 實現並行Golang並行
- 【Java併發】【AQS鎖】鎖在原始碼中的應用JavaAQS原始碼
- python 協程 自定義互斥鎖Python
- 使用 python 實現簡單的共享鎖和排他鎖Python
- 鎖的使用與死鎖的避免
- python之GIL全域性直譯器鎖,自定義互斥鎖,死鎖與遞迴鎖Python遞迴
- Mariadb之顯式使用表鎖和行級鎖
- 解鎖自定義分享功能新姿勢
- Android 自定義View 滑動解鎖AndroidView
- golang中的鎖競爭問題Golang
- Java中鎖的實現方式Java
- Java中的公平鎖和非公平鎖Java
- 在Flutter中使用自定義IconFlutter
- 在.NET 6.0上使用Kestrel配置和自定義HTTPSHTTP
- golang 使用 viper 讀取自定義配置檔案Golang
- golang中channel的用法Golang
- Mysql中S 鎖和 X 鎖的區別MySql
- 如何在Linux中鎖定和解鎖多個使用者Linux
- 同步控制和鎖,ReenterLock和Condition的詳細使用
- oracle使用者鎖住、過期處理方式Oracle
- 分散式鎖-Redission-Lock鎖的使用與原理分散式Redis
- [譯] Part 31: Golang 中的自定義ErrorGolangError
- redis分散式鎖-spring boot aop+自定義註解實現分散式鎖Redis分散式Spring Boot
- MySQL使用之五_自定義函式和自定義過程MySql函式
- 利用MySQL中的樂觀鎖和悲觀鎖實現分散式鎖MySql分散式
- 如何使用終端在macOS Big Sur Finder中鎖定檔案?Mac
- Go死鎖——當Channel遇上Mutex時GoMutex