Go初識-day13-包(package)、線程同步(sync)與atomic操作

包 (package)

  • Golang目前約有150個標準包
  • 官網有所有package的文檔 https://golang.org/pkg/
  • 第三方package的資源多位於github

安裝第三方包

再命令行輸入

1
go get github網址

會安裝第三方包,默認會安裝到%GOPATH%/src/ 中


線程同步 (sync) 與鎖 (lock)

  • 多個線程之間可能同時使用同一個資源
  • 資源在線程之間共享,加鎖可以使資源更安全的被取用或是寫入
  • 但在進行只有讀取操作時,不加鎖可讓執行效率更高

情況一: 資源共享讀取寫入 — 不加鎖

在不加鎖的情況下,開啟多個線程同時去對map操作

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
)

func goroutine_write(dic map[int]string){
for i:=0;i<5;i++{
go func(source map[int]string){
source[1] = fmt.Sprintf("%d%d%d",i,i,i)
}(dic)
}
fmt.Println(dic)
}

func main(){
a := make(map[int]string)
a[1] = "value1"

goroutine_write(a)
}

result 1

1
map[1:444]

result 2

1
map[1:555]

result 3

1
map[1:222]

conclusion

我們可以看到每次的結果都不一樣,證明了線程之間存在競爭關係

  • 哪個線程得到最後修改的權限,值就會是該線程修改的結果

這不是理想執行的情況,會造成資源取用不安全
在編譯時可透過-race參數來檢查是否存在線程競爭,如果存在,編譯完成後,執行時會發出警告

1
go build -race  go_dev/day12/example/example_sync


情況二: 資源共享讀取寫入 — 互斥鎖

  • 互斥鎖:不管讀的線程 或是 寫的線程 每次都只能有一個線程進入到加鎖的代碼中去執行代碼
  • 線程鎖相關的package於”sync”包中
    • var 變量 sync.Mutex: 定義一個”互斥鎖”類型
    • 變量.Lock():加鎖
    • 變量.Unlock() :釋放鎖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"sync"
"math/rand"
"time"
)

var lock sync.Mutex //[1]

func goroutine_write(dic map[int]int){
for i:=0;i<5;i++{
go func(source map[int]int){
// 賦值寫入線程需加鎖
lock.Lock()
source[1] = rand.Intn(100)
lock.Unlock() //釋放鎖
}(dic)
}
//讀取線程也須加鎖
lock.Lock()
fmt.Println(dic)
lock.Unlock()

time.Sleep(time.Second) //[2]
}

func main(){
a := make(map[int]int)
a[1] = 1
goroutine_write(a)
}

[1] 定義一個變量lock為互斥鎖類型(sync.Mutex)
[2] 不要讓主線程直接結束,故sleep一段時間之後等待子線程都執行完畢再結束

result

1
map[1:59]

情況三: 資源共享讀取寫入 — 讀寫鎖

  • 讀寫鎖:一種特殊鎖,對共享資源訪問者劃分讀者寫者讀操作可並發重入寫操作是互斥的
    讀線程進到讀寫鎖中,其他 讀線程 仍能進入,但 寫線程 不能進入
    寫線程進到讀寫鎖中,其他 讀線程 不能進入,且 寫線程 亦不能進入

  • 讀多寫少的情況下,讀寫鎖性能最高

  • var 變量 sync.RWMutex: 定義一個”讀寫鎖”類型

讀者鎖:

  • 變量.RLock():只有讀線程可以進入RLock鎖,且讀線程在執行時其他讀線程仍能進入鎖中,與RUnlock之間通常為讀取相關的操作
  • 變量.RUnlock(): 釋放鎖

互斥鎖:

  • 變量.Lock():只有寫線程可以進入Lock鎖,且寫線程在執行時其他不管是讀或是寫線程皆無法進入鎖中,與Unlock之間通常為寫入相關的操作
  • 變量.Unlock(): 釋放鎖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"fmt"
"sync"
"math/rand"
"time"
)

var RWlock sync.RWMutex

func RWLock_test(dic map[int]int){

//寫操作
for i:=0;i<5;i++{
go func(source map[int]int){
RWlock.Lock() //只有寫線程可以進入.Lock()鎖,其他讀寫線程皆無法進入
source[1] = rand.Intn(100)
RWlock.Unlock()
}(dic)
}

//讀操作
for i:=0;i<100;i++{
go func (read map[int]int){
RWlock.RLock() //只有讀線程可以進入.RLock()鎖,且讀線程在執行時其他讀線程仍能繼續執行
fmt.Println(read)
RWlock.RUnlock()
}(dic)
}

time.Sleep(time.Second*10)
}

func main(){
a := make(map[int]int)
a[1] = 1
a[2] = 2
a[3] = 3
a[4] = 4
RWLock_test(a)
}

result

1
2
3
4
5
6
7
8
9
map[1:81 2:2 3:3 4:4]
map[4:4 1:81 2:2 3:3]
map[1:81 2:2 3:3 4:4]
map[1:81 2:2 3:3 4:4]
map[1:81 2:2 3:3 4:4]
.
.
.
map[3:3 4:4 1:81 2:2]

atomic package

位於sync/atomic包中
*atomic.AddInt相關函數,可讓整數同一時間只會有一個線程能修改此數,絕對不存在兩個或多個線程同時操作atomic.AddInt中的整數

  • 使用atomic.AddInt可對同一時間併發多少線程記數
  • 但是記數完時需要打印該值,仍會牽扯到線程互相競爭打印該值的問題
  • 透過atomic.LoadInt相關函數,可直接讀取該值

用法

int32類型作為示範

1
2
3
atomic.Addint32(int32類型整數的指針,每次增加的差值)

atomic.LoadInt32(int32類型整數的指針)

  • 其他方法可參考官方文檔

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import (
"fmt"
"sync"
"math/rand"
"time"
"sync/atomic"
)

var RWlock sync.RWMutex

func RWLock_test(dic map[int]int){

var count int32 //聲明int32類型的count變量

//寫操作
for i:=0;i<5;i++{
go func(source map[int]int){
RWlock.Lock()
source[1] = rand.Intn(100)
RWlock.Unlock()
}(dic)
}

//讀操作
for i:=0;i<1000;i++{
go func (read map[int]int){
RWlock.RLock()
fmt.Println(read)
RWlock.RUnlock()
atomic.AddInt32(&count,1) //於線程通過鎖時記數
}(dic)
}

time.Sleep(time.Second*5)
fmt.Println(atomic.LoadInt32(&count)) //讀取最新的記數值
}

func main(){
a := make(map[int]int)
a[1] = 1
a[2] = 2
a[3] = 3
a[4] = 4
RWLock_test(a)
}

result

1
2
3
4
5
6
7
8
9
10
map[1:81 2:2 3:3 4:4]
map[2:2 3:3 4:4 1:81]
map[1:81 2:2 3:3 4:4]
map[1:81 2:2 3:3 4:4]
map[1:81 2:2 3:3 4:4]
.
.
.
map[2:2 3:3 4:4 1:81]
1000