Go基礎之鎖的初識

2022-01-29 06:48:34 字數 3405 閱讀 4506

當我們的程式就乙個執行緒的時候是不需要用到鎖的,但是通常我們實際的**不會是單個執行緒的,所有這個時候就需要用到鎖了,那麼關於鎖的使用場景主要涉及到哪些呢?

互斥鎖:同乙個時刻只有乙個執行緒能夠拿到鎖

我們先通過乙個例子來演示,如果當多個執行緒同時更改乙個變數,結果會是怎麼樣

不加鎖版本

package main

import (

"sync"

"fmt")

var (

//lock sync.mutex

count

intw sync.waitgroup //用於等待子執行緒執行完之後退出

)func main()

w.done() //執行完 執行w.done

}()for i :=0;i<100000;i++

w.wait() //最後執行w.wait等待所有的執行緒執行完畢

fmt.println(count)

}

當我們執行多次就可以發現,最後的結果基本不可能是我們先看到的:200000

我們修改****需要加鎖保護的地方加上鎖,並且這裡加的是互斥鎖,修改後的**為:

package main

import (

"sync"

"fmt")

var (

lock sync.mutex

count

intw sync.waitgroup //用於等待子執行緒執行完之後退出

)func main()

w.done() //執行完 執行w.done

}()for i :=0;i<100000;i++

w.wait() //最後執行w.wait等待所有的執行緒執行完畢

fmt.println(count)

}

這次當我們多次執行的時候,就能保證我們每次都能看到我們想要的值:200000

接下來看讀寫鎖

讀寫鎖主要用到讀多寫少的場景

讀寫鎖分為:讀鎖和寫鎖

如果自己設定了乙個寫鎖,那麼其他讀的執行緒以及寫的執行緒都拿不到鎖,這個時候和互斥鎖的功能相同

如果自己設定了乙個讀鎖,那麼其他寫的執行緒是拿不到鎖的,但是其他讀的執行緒都是可以拿到這個鎖

我們把上面的例子**進行更改:

package main

import (

"sync"

"fmt")

var (

rwlock sync.rwmutex

w sync.waitgroup

count

int)

func main()

w.done()

}()for i:=0;i<1000000;i++

w.wait()

fmt.println(count)

}

通過設定寫鎖,我們同樣可以實現資料的一致性

下面是乙個讀鎖的使用例子:

package main

import (

"sync"

"fmt")

var (

rwlock sync.rwmutex

w sync.waitgroup

count

int)

func main()

w.done()

}()for i:=0;i<16;i++()

}w.wait()

fmt.println(count)

}

原子操作,我們則不需加鎖,也能保證資料的一致性

並且如果只是計算,那麼原子操作則是最快的

例項**:

package main

import (

"sync"

//"time"

"sync/atomic"

"fmt")

var (

w sync.waitgroup

count int32

)func main()

w.done()

}()for i:=0;i<1000000;i++

w.wait()

//end :=time.now().unixnano()

start)/1000/1000)

fmt.println(count)

}

互斥鎖需要注意的問題:

1、不要重複鎖定互斥鎖

2、不要忘記解鎖互斥鎖, 必要時使用defer語句

3、不要對尚未鎖定或者已解鎖的互斥鎖解鎖

4、不要對在多個函式之間直接傳遞互斥鎖

對已經鎖定的互斥鎖進行鎖定,會立即阻塞當前的goroutine 這個goroutine所執行的流程會一直停滯在該呼叫互斥鎖的lock方法的那行**

所謂死鎖: 當前程式中的主goroutine以及我們啟用的那些goroutine 都已經被阻塞,這些goroutine可以被稱為使用者級的goroutine 這就相當於整個程式已經停滯不前了,並且這個時候go程式會丟擲如下的panic:

fatal error: all goroutines are asleep - deadlock!

並且go語言執行時系統丟擲自行丟擲的panic都屬於致命性錯誤,都是無法被恢復的,呼叫recover函式對他們起不到任何作用

go語言中的互斥鎖是開箱即用的,也就是我們宣告乙個sync.mutex 型別的變數,就可以直接使用它了,需要注意:該型別是乙個結構體型別,屬於值型別的一種,把它傳給乙個函式

將它從函式中返回,把它賦值給其他變數,讓它進入某個通道都會導致他的副本的產生。並且原值和副本以及多個副本之間是完全獨立的,他們都是不同的互斥鎖

所以不應該將鎖通過函式的引數進行傳遞

1、在寫鎖已被鎖定的情況下再次試圖鎖定寫鎖,會阻塞當前的goroutine

2、在寫鎖已被鎖定的情況下再次試圖鎖定讀鎖,也會阻塞當前的goroutine

3、在讀鎖已被鎖定的情況下試圖鎖定寫鎖,同樣會阻塞當前的goroutine

4、在讀鎖已被鎖定的情況下再試圖鎖定讀鎖,並不會阻塞當前的goroutine

對於某個受到讀寫鎖保護的共享資源,多個寫操作不能同時進行,寫操作和讀操作也不能同時進行,但多個讀操作卻可以同時進行

對寫鎖進行解鎖,會喚醒「所有因試圖鎖定讀鎖,而被阻塞的goroutine」, 並且這個通常會使他們都成功完成對讀鎖的鎖定

對讀鎖進行解鎖,只會在沒有其他讀鎖鎖定的前提下,喚醒「因試圖鎖定寫鎖,而被阻塞的goroutine」 並且只會有乙個被喚醒的goroutine能夠成功完成對寫鎖的鎖定,其他的goroutine

還要在原處繼續等待,至於哪乙個goroutine,那麼就要看誰等待的事件最長

解鎖讀寫鎖中未被鎖定的寫鎖, 會立即引發panic ,對其中的讀鎖也是如此,並且同樣是不可恢復的

Go基礎之 介面

在go語言中,乙個類只要實現了介面要求的所有函式,我們就說這個類實現了該介面 inte ce型別可以定義一組方法,用來表示乙個物件的行為特徵,inte ce不能包含任何變數,介面是引用型別。舉個簡單的例子,乙個動物的介面,動物有吃的能力,有叫的能力,等等,這裡省略,假如動物就只有吃和叫的能力。pac...

GO基礎之陣列

一 陣列的宣告與遍歷 package main import fmt 宣告陣列的形式1 var arr 3 int var arr2 4 int func main fmt.println a b int 遍歷陣列方式1 for i 0 i fmt.println 遍歷陣列方式2 for value...

Go 基礎之指標

區別於c c 中的指標,go語言中的指標不能進行偏移和運算,是安全指標。任何程式資料載入記憶體後,在記憶體都有他們的位址,這就是指標。而為了儲存乙個資料在記憶體中的位址,我們就需要指標變數。比如,永遠不要高估自己 這句話是我的座右銘,我想把它寫入程式中,程式一啟動這句話是要載入到記憶體 假設記憶體位...