問題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會更適用?
想知道有沒有這方面的場景比較分享呢?
因為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住的問題
了解,也有找到另一篇文章,評論提到是否用鎖,或是否用chan都是看使用場景使用,雖然golang有說Do not communicate by sharing memory; instead, share memory by communicating.” 但被認為是宣傳channel的口號(?XD)
這是因為以前在設計多線程的時候
是用共享記憶體的方式去實現IPC
這會在你系統越做越大,越做越複雜的時候產生你共享記憶體越來越大的問題
相當於你有一個public data pool,你把需要的資料通通丟進去進行共享
雖說開channel也是需要消耗資源,但可以解決上述問題
直接針對你需要的資源開闢一條頻道(channel)直接進行溝通