iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 27
1

Goroutine就像變了心的女友一樣回不去了,當執行Goroutine發生error時,不就都沒有辦法知道嗎?
https://ithelp.ithome.com.tw/upload/images/20201004/20129515jXYdLK7rH3.jpg
只好靠elk或是大軍的特異功能
https://ithelp.ithome.com.tw/upload/images/20201004/20129515FSwEXWu91z.jpg

其實Golang有一個叫errgroup的套件可以做到

Goroutine拋錯的處理-errgroup

直接引用golang.org/x/sync/errgroup 不需要額外go get套件,使用方式跟一般的Goroutine微不同,
以下為實作的code

package main

import (
	"fmt"
	"net/http"

	"golang.org/x/sync/errgroup"
)

func main() {
    //宣告一個errgroup.Group
	var g errgroup.Group
	var urls = []string{
		"http://www.golang.org/",
		"http://www.google.com/",
		"http://www.somestupidname.com/",
	}
	for _, url := range urls {
		// 小心,這邊是上篇中有提到的closure的踩雷點
		url := url // https://golang.org/doc/faq#closures_and_goroutines
        //不是直接用go func(),而是用func (g *Group) Go(f func() error)
		g.Go(func() error {
			// Fetch the URL.
			resp, err := http.Get(url)
			if err == nil {
				resp.Body.Close()
			}
            //有發現嗎,這邊有回傳error
			return err
		})
	}
	// 用errgroup除了攔劫error外,一樣也可以做到waitgroup的事情,
	if err := g.Wait(); err == nil {
		fmt.Println("Successfully fetched all URLs.")
	}
}

errgroup也支援使用context,如果要做上下層context管理的話,可以透過errgroup.WithContext進行Goroutine

g, ctx := errgroup.WithContext(context.Background())

使用Goroutine的注意的課題-Data Race

Race Condition(競爭危害)

競爭危害(race hazard)又名競態條件、競爭條件(race condition),它旨在描述一個系統或者進程的輸出依賴於不受控制的事件出現順序或者出現時機。此詞源自於兩個訊號試著彼此競爭,來影響誰先輸出。
舉例來說,如果電腦中的兩個行程同時試圖修改一個共享記憶體的內容,在沒有並行控制的情況下,最後的結果依賴於兩個行程的執行順序與時機。而且如果發生了並行存取衝突,則最後的結果是不正確的。
競爭危害常見於不良設計的電子系統,尤其是邏輯電路。但它們在軟體中也比較常見,尤其是有採用多執行緒技術的軟體。

from wiki 競爭危害

用上面寫的code來示範一次

var respBody string
	for _, url := range urls {

		url := url
		g.Go(func() error {
			// Fetch the URL.
			resp, err := http.Get(url)
			if err == nil {
				defer resp.Body.Close()
				body1, err1 := ioutil.ReadAll(resp.Body)
				if err1 != nil {
					return err1
				}
				respBody = string(body1)
			}
			return err
		})

上面的code執行後會發生什麼事情?
答案是回傳值每次都不一樣或是噴出data race的錯誤,因為Goroutine都對respBody這變數進行存取

要如何解決呢?
有下面幾種方式

sync.Mutex 互斥鎖

sync.Mutex簡單說就是把變數鎖上不讓其他goroutine存取到此變數,等正在使用的goroutine做完解鎖後就換另一個goroutine使用,就是排隊輪流用啦

var respBody string
    //宣告互斥鎖
	var m sync.Mutex
	for _, url := range urls {

		url := url
		g.Go(func() error {
			// Fetch the URL.
			resp, err := http.Get(url)
			if err == nil {
				defer resp.Body.Close()
				body1, err1 := ioutil.ReadAll(resp.Body)
				if err1 != nil {
					return err1
				}
                //上鎖
				m.Lock()
				respBody = string(body1)
                //存取完變數後解鎖
				m.Unlock()
			}
			return err
		})
	}

sync.RWMutex

sync.RWMutex是個讀寫互斥鎖(multiple readers, single writer lock),多個讀取單筆寫入,
如果數個Goroutine同時對一個會有寫入行為的變數進行讀取的話,沒Lock的話會造成data race,
但是如果每次讀取都要lock的話就會讓效能整個低落,所以才要改用RWMutex,
RWMutex雖然還是要讀取時也要加上RWLock()與RWUnlock(),但是如果沒有寫入行為時就不會進行鎖定,
使用方式很簡單

//原本的sync.Mutex
var m sync.Mutex
//換成sync.RWMutex
var rwm sync.RWMutex
//原本上鎖
m.Lock()
respBody = string(body1)
m.Unlock()
//改成
m.RLock()
respBody = string(body1)
m.RUnlock()

如何偵測是否會data race

golang本身有提供一個語法進行確認是否有data race的情形,但是有個缺點就是如果是屬於很難得踩到的data race,這個語法不一定偵測出來

go run -race main.go

Goroutine使用map

官方文章有提到map非原子操作,所以如果用goroutine去讀寫map的話,會發生data race的情形,
以下方例子來說,雖然goroutine操作的map對應的key值是不同的,但是還是一樣會噴出data race的錯誤,
就算加上RWMutex也是一樣

func main() {
	var g errgroup.Group
	mp := make(map[string]string)
	g.Go(func() error {
		mp["A"] = "BAD"
		return nil
	})
	g.Go(func() error {
		mp["B"] = "GOOD"
		return nil
	})
	if err := g.Wait(); err != nil {
		fmt.Println("goroutine err:", err)
	}
}
//Found 1 data race(s)

sync.Map解決Goroutine使用map的最佳方案

func main() {
	var g errgroup.Group
	var m sync.Map

	g.Go(func() error {
		m.Store("A", "BAD")
		return nil
	})
	g.Go(func() error {
		m.Store("B", "GOOD")
		return nil
	})
	if err := g.Wait(); err != nil {
		fmt.Println("goroutine err:", err)
	}
	v, ok := m.Load("A")
	if ok {
		fmt.Println("it's an existing key,value is ", v)
	} else {
		fmt.Println("it's an unknown key")
	}
	v, ok = m.Load("B")
	if ok {
		fmt.Println("it's an existing key,value is ", v)
	} else {
		fmt.Println("it's an unknown key")
	}

}

執行結果,不再噴data race惹

it's an existing key,value is  BAD
it's an existing key,value is  GOOD

上一篇
[DAY26]Golang最強大的特性:Goroutine -1
下一篇
[DAY28]Goroutine的好碰友-Channel
系列文
欸你這週GO了嘛30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言