iT邦幫忙

0

[golang] 使用channel與鎖遇到狀況的選用?

  • 分享至 

  • twitterImage

問題1 :
golang在講到共享變數時都會建議採用channel處理,
"Do not communicate by sharing memory; instead, share memory by communicating.”

於是我這邊試著改寫以下銀行範例,使用channel來做資料的處理
https://clouding.city/go/mutex-rwmutex/
但是使用go run -race main.go時仍會出現data race,
((XX已解決)請問我的改法是哪裡出問題了呢?)
更正:原因似乎出在b.ReadBalance(),於是做了更正2的修正.延伸出問題2

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	b := &Bank{}
	n := 10000
	wg.Add(n)
	valueCh := make(chan int)
	go b.ListenChanHandler(valueCh)
	for i := 1; i <= n; i++ {
		go func() {
			b.Deposit(100, valueCh) //同時存款
			wg.Done()
		}()

	}
	wg.Wait()
	fmt.Println(b.ReadBalance()) //印出 這個拿掉就不會data race
	fmt.Println("done")
}

type Bank struct {
	balance int
}

func (b *Bank) Deposit(amount int, valueCh chan int) {

	valueCh <- amount
}

func (b *Bank) ListenChanHandler(valueCh chan int) {
	go func() {
		for {
			select {
			case e := <-valueCh:
				b.balance = b.balance + e
			}
		}
	}()
}

func (b *Bank) ReadBalance() (balnce int) {
	balance := b.balance
	// fmt.Println("balance:", balance)
	return balance
}

更正2:
後來又用了鎖+上channel一起用,data race就消失了


https://clouding.city/go/mutex-rwmutex/
package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	b := &Bank{}
	n := 10000
	wg.Add(n)
	valueCh := make(chan int)
	go b.ListenChanHandler(valueCh)
	for i := 1; i <= n; i++ {
		go func() {
			b.Deposit(100, valueCh) //同時存款
			wg.Done()
		}()

	}
	wg.Wait()
	fmt.Println(b.ReadBalance()) 
	fmt.Println("done")
}

type Bank struct {
	balance int
+	mux     sync.Mutex
}

func (b *Bank) Deposit(amount int, valueCh chan int) {

	valueCh <- amount
}

func (b *Bank) ListenChanHandler(valueCh chan int) {
	go func() {
		for {
			select {
			case e := <-valueCh:
+                               b.mux.Lock()
				b.balance = b.balance + e
+                               b.mux.Unlock()
			}
		}
	}()
}

func (b *Bank) ReadBalance() (balnce int) {
+	b.mux.Lock()
	balance := b.balance
+	b.mux.Unlock()
	// fmt.Println("balance:", balance)
	return balance
}

問題2: 竟然如此為什麼要用channel,這樣情況下單用原本的範例鎖會不會比較快?
好像不需要用上channel比較簡潔,
還是普遍有同時讀同時寫就使用鎖+channel會更適用?
想知道有沒有這方面的場景比較分享呢?

回問題2:
channel 其實比sync.WaitGroup 來的好用的!
用在goroutine的控制(搭配select)或者取得goruntine 任何斷點的結果(類似Javascript中listen event的效果)很好用
簡單來說,當需要聽goroutine 執行到一半的資料,或者當a goroutine執行到一個程度,通知b goroutine 開始運作...等,進一步控制goroutine 的需求時,channel 很不錯用
nagiMemo iT邦新手 5 級 ‧ 2021-08-07 11:22:19 檢舉
新手一開始真的很排斥channel (能不用就不用) 但後來發現用在goroutine通信真的很方便!
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 個回答

0
whitefloor
iT邦研究生 2 級 ‧ 2021-08-04 11:51:32
最佳解答
  • 問題一

因為b.balance會被main這個routine跟你額外開的goroutine共用變數
所以實際上來看是有兩個routine共享變數
故會產生data race
更好的改法可以用RWLock,多讀單寫,效率更好

關於channel跟goroutine可以看看下面兩個網址,裡面有案例跟解釋可以解決你的疑惑
常見的併發模式
Effective Go : Concurrency - Share by communicating

  • 問題二

channel跟sync package是不同概念的東西
channel是提供給goroutine之間分享資訊使用
sync package是你在併發時防止data race的情況使用

會不會比較快這個問題
牽扯到你的worker設計,以及場景使用,很難一論敘述說哪個比較適合,哪個比較快
但是可以肯定的是,如果你的sync.lock鎖太長,會有太多routine要處理時有hang住的問題

nagiMemo iT邦新手 5 級 ‧ 2021-08-07 11:18:32 檢舉

了解,也有找到另一篇文章,評論提到是否用鎖,或是否用chan都是看使用場景使用,雖然golang有說Do not communicate by sharing memory; instead, share memory by communicating.” 但被認為是宣傳channel的口號(?XD)

whitefloor iT邦研究生 2 級 ‧ 2021-08-09 10:25:59 檢舉

這是因為以前在設計多線程的時候
是用共享記憶體的方式去實現IPC
這會在你系統越做越大,越做越複雜的時候產生你共享記憶體越來越大的問題
相當於你有一個public data pool,你把需要的資料通通丟進去進行共享

雖說開channel也是需要消耗資源,但可以解決上述問題
直接針對你需要的資源開闢一條頻道(channel)直接進行溝通

我要發表回答

立即登入回答