iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 17
3

Goroutines

Goroutines就是一系列的Thread操作,意思即一支程式同時進行好幾個小程式。使用go的時候程式會將go所要執行的項目放到背景執行,繼續進行主程式的程式碼


Without go

在完全沒有使用go的情況下進行這一支程式

package main

import "fmt"

func foo() {
    for i := 1; i <= 10; i++ {
        fmt.Println("Foo: ", i)
    }
}

func bar() {
    for i := 1; i <= 10; i++ {
        fmt.Println("Foo: ", i)
    }
}

func main() {
    foo()
    bar()
}

在Go Playground上執行


With go

那如果在執行function前加上go呢?

go foo()
go bar()

完整程式碼:

package main

import "fmt"

func foo() {
    for i := 1; i <= 10; i++ {
        fmt.Println("Foo: ", i)
    }
}

func bar() {
    for i := 1; i <= 10; i++ {
        fmt.Println("Foo: ", i)
    }
}

func main() {
    go foo()
    go bar()
}

在Go Playground上執行

這時候大家可能會發現好像甚麼都沒有執行,原因在於在執行go foo()和go bar()之後,系統將這兩個項目放到背景執行,而接著馬上就是程式main的尾端,代表程式已經結束。所以放到背景執行的foo()和bar()還來不及執行程式就結束了


WaitGroup

有鑑於剛剛背景Thread所執行的項目都還沒開始程式就結束了,WaitGroup的功能即為等待這個Group的項目執行完再繼續進行

首先,因為WaitGroup是包含在package sync裏頭的,所以需要引入一下

import "sync"

需要宣告一個WaitGroup變數

var wg sync.WaitGroup

當WaitGroup裏頭的空間等於0的時候就會繼續進行主程式,下面會介紹一些操作方式

Add()

增加WaitGroup裡可以容納個Thread

wg.Add(2)

Wait()

等待WaitGroup裡的Thread執行完畢再繼續進行

wg.Wait()

Done() 或是 Add(-1)

使WaitGroup的容量-1

wg.Done()

或是

wg.Add(-1)

有了這些基本操作後,我們可以改良剛剛的程式

package main

import (
    "fmt"
    "sync"
)

func foo() {
    for i := 1; i <= 10; i++ {
        fmt.Println("Foo: ", i)
    }
    wg.Done()
}

func bar() {
    for i := 1; i <= 10; i++ {
        fmt.Println("Foo: ", i)
    }
    wg.Done()
}

var wg sync.WaitGroup

func main() {
    wg.Add(2)
    go foo()
    go bar()
    wg.Wait()
}

在Go Playground上執行

這時候Thread裡的內容就有確實執行完畢了


Sleep

讓程式暫停

這個功能是放在package time裡面的,所以又要引入了

import "time"

使用方法:

time.Sleep(5 * time.Millisecond)

這個範例是暫停五秒鐘


Race condition

有了go這個好用的東西可以分成好幾個Thread同時進行,那就有可能會有同時搶奪資源的情況。比方說兩個Thread同時都要讀取並修改同一個變數,拿一個情境題來舉例:

如果今天同一個銀行的網路銀行有提款功能,而有人同時在兩台電腦都登入了要提款,兩台電腦都送出了提領1000的請求會怎樣呢?

package main

import (
    "fmt"
    "sync"
    "time"
)

func withdraw() {
    balance := money
    time.Sleep(3000 * time.Millisecond)
    balance -= 1000
    money = balance
    fmt.Println("After withdrawing $1000, balace: ", money)
    wg.Done()
}

var wg sync.WaitGroup
var money int = 1500

func main() {
    fmt.Println("We have $1500")
    wg.Add(2)
    go withdraw() // first withdraw
    go withdraw() // second withdraw
    wg.Wait()
}

在Go Playground上執行

到最後大家會發現1500提領了兩次1000還剩500?為甚麼呢?

因為在第一次提領的時候,系統先讀取餘額為1500元,同時第二台電腦也登入了餘額也是顯示為1500元,這時候就是因為兩邊同時搶著讀取餘額的原因,所以第一次提領1000元時回報給系統"餘額剩500元",第二台領了1000元也回報系統"餘額剩500元"

不過Golang的編譯器可以檢查是不是有race condition,只要在平常執行go run後面加上參數-race即可

$ go run -race main.go

Mutex

那要如何避免這樣的情況發生呢?Mutex可以解決上面這樣的問題。這個也是在package sync裏頭

import "sync"

先宣告一個Mutex變數

var mu sync.Mutex

Lock()

當使用mu.Lock()的時候,之後所用到的變數就會上鎖,只有在使用中的Thread可以存取,其他都需要等到釋放後才能存取

mu.Lock()

Unlock()

釋放lock的變數

mu.Unlock()

所以我可以將剛剛withdraw()改良

func withdraw() {
    mu.Lock()
    balance := money
    time.Sleep(3000 * time.Millisecond)
    balance -= 1000
    money = balance
    mu.Unlock()
    fmt.Println("After withdrawing $1000, balace: ", money)
    wg.Done()
}

因為Lock()和Unlock()通常都會一起出現,所以有些人會這樣寫

func withdraw() {
    {
        mu.Lock()
        balance := money
        time.Sleep(3000 * time.Millisecond)
        balance -= 1000
        money = balance
        mu.Unlock()
    }
    fmt.Println("After withdrawing $1000, balace: ", money)
    wg.Done()
}

全部的程式碼

package main

import (
    "fmt"
    "sync"
    "time"
)

func withdraw() {
    mu.Lock()
    balance := money
    time.Sleep(3000 * time.Millisecond)
    balance -= 1000
    money = balance
    mu.Unlock()
    fmt.Println("After withdrawing $1000, balace: ", money)
    wg.Done()
}

var wg sync.WaitGroup
var money int = 1500
var mu sync.Mutex

func main() {
    fmt.Println("We have $1500")
    wg.Add(2)
    go withdraw() // first withdraw
    go withdraw() // second withdraw
    wg.Wait()
}

在Go Playground上執行

如此一來就解決race condition的問題了


上一篇
30天就Go(15):Interface
下一篇
30天就Go(18):Error Handling
系列文
30天就Go:教你打造LINE自動回話機器人23
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言