自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用Go語(yǔ)言時(shí),謹(jǐn)防鎖拷貝!

開發(fā) 后端
相信大家對(duì) Go 語(yǔ)言的鎖拷貝問題并不陌生,那我們應(yīng)該如何規(guī)范使用Go 語(yǔ)言才能規(guī)避這個(gè)問題呢?一起來(lái)看作者是如何處理的。

[[413679]]

本文轉(zhuǎn)載自微信公眾號(hào)「Golang來(lái)啦」,作者Seekload。轉(zhuǎn)載本文請(qǐng)聯(lián)系Golang來(lái)啦公眾號(hào)。

四哥水平有限,如有翻譯或理解錯(cuò)誤,煩請(qǐng)幫忙指出,感謝!

相信大家對(duì) Go 語(yǔ)言的鎖拷貝問題并不陌生,那我們應(yīng)該如何規(guī)范使用Go 語(yǔ)言才能規(guī)避這個(gè)問題呢?一起來(lái)看作者是如何處理的。

原文如下:

假設(shè)我們有一個(gè)包含 map 的結(jié)構(gòu)體,現(xiàn)在想在方法中修改這個(gè) map,看下面的例子[1]:

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. type Container struct { 
  6.   counters map[string]int 
  7.  
  8. func (c Container) inc(name string) { 
  9.   c.counters[name]++ 
  10.  
  11. func main() { 
  12.   c := Container{counters: map[string]int{"a": 0, "b": 0}} 
  13.  
  14.   doIncrement := func(name string, n int) { 
  15.     for i := 0; i < n; i++ { 
  16.       c.inc(name
  17.     } 
  18.   } 
  19.  
  20.   doIncrement("a", 100000) 
  21.  
  22.   fmt.Println(c.counters) 

Container 包含一個(gè)計(jì)數(shù)器集合,按 name 區(qū)分。inc() 會(huì)按 name 對(duì)相應(yīng)的計(jì)數(shù)器執(zhí)行自增操作(假設(shè)計(jì)數(shù)器存在)。main() 里循環(huán)多次調(diào)用 inc()。

執(zhí)行上面的代碼,輸出:

  1. map[a:100000 b:0] 

現(xiàn)在假設(shè)有兩個(gè) goroutine 會(huì)并發(fā)地調(diào)用 inc()。因?yàn)槲覀儽仨毿⌒母?jìng)爭(zhēng)條件,所以使用了 Mutex 保護(hù)臨界區(qū)。

  1. package main 
  2.  
  3. import ( 
  4.   "fmt" 
  5.   "sync" 
  6.   "time" 
  7.  
  8. type Container struct { 
  9.   sync.Mutex                       // <-- Added a mutex 
  10.   counters map[string]int 
  11.  
  12. func (c Container) inc(name string) { 
  13.   c.Lock()                         // <-- Added locking of the mutex 
  14.   defer c.Unlock() 
  15.   c.counters[name]++ 
  16.  
  17. func main() { 
  18.   c := Container{counters: map[string]int{"a": 0, "b": 0}} 
  19.  
  20.   doIncrement := func(name string, n int) { 
  21.     for i := 0; i < n; i++ { 
  22.       c.inc(name
  23.     } 
  24.   } 
  25.  
  26.   go doIncrement("a", 100000) 
  27.   go doIncrement("a", 100000) 
  28.  
  29.   // Wait a bit for the goroutines to finish 
  30.   time.Sleep(300 * time.Millisecond) 
  31.   fmt.Println(c.counters) 

你期望上面這段代碼會(huì)輸出什么呢?我得到的結(jié)果是這樣的:

  1. func (c *Container) inc(name string) { 
  2.   c.Lock() 
  3.   defer c.Unlock() 
  4.   c.counters[name]++ 

我們使用 mutex 時(shí)已經(jīng)很小心了,怎么還會(huì)出問題呢?你覺得應(yīng)該如何修復(fù)這個(gè)問題?提示:只需要改動(dòng)一個(gè)字符的代碼就可以了!

代碼的問題在于,無(wú)論何時(shí)調(diào)用 inc(),c 都會(huì)是一份拷貝,因?yàn)?inc() 是定義在 Container 上,而非 *Container;換句話說,c 是值接受者,而不是指針接受者。因此,inc() 并不能真正修改 c 的內(nèi)容。

但等等,文章第一個(gè)示例是如何工作的?在單協(xié)程的例子中,c 也是按值傳遞,但是為什么能得到正確的結(jié)果 -- 在 inc() 在對(duì) map 所做的修改,能影響到 main() 函數(shù)的原始值。這是因?yàn)?map 是引用類型而非值類型。Container 里保存的是指向 map 的指針,而不是 map 實(shí)際的數(shù)據(jù)。所以即使我們創(chuàng)建 Container 的副本,counters 保存的仍是指向 map 的地址。

所以文章第一個(gè)例子也是存在問題的,盡管執(zhí)行結(jié)果沒有問題,但是使用方法不符合官方指南[2] - 在方法中對(duì)原始數(shù)據(jù)進(jìn)行修改,則方法應(yīng)定義成指針方法,而非值方法。這里對(duì) map 的使用給了我們一種錯(cuò)誤的提示。作為練習(xí),可以將第一個(gè)示例中的 map 換成 int 類型的計(jì)數(shù)器,并注意觀察 inc() 的副本是如何遞增的,在 inc() 中對(duì)副本做的修改不會(huì)影響到 main() 中的原始值。

Mutex 是值類型(可以看 Go 文檔[3]相關(guān)的定義,包括注釋里也明確地提示不能拷貝),復(fù)制再使用是錯(cuò)誤的。復(fù)制僅僅是創(chuàng)建了一個(gè)新的 mutex,很顯然地,對(duì)計(jì)數(shù)器的互斥使用就失效了。

所以應(yīng)該這樣修改,定義 inc() 方法時(shí)在 Container 之前添加 *:

  1. func (c *Container) inc(name string) { 
  2.   c.Lock() 
  3.   defer c.Unlock() 
  4.   c.counters[name]++ 

c 通過指針方式傳到方法中,指向的 Container 與 main() 函數(shù)里面的是同一個(gè)。

這個(gè)問題并不罕見,事實(shí)上,使用 go vet 命令就會(huì)發(fā)現(xiàn)這個(gè)問題:

  1. $ go tool vet method-mutex-value-receiver.go 
  2. method-mutex-value-receiver.go:19: inc passes lock by value: main.Container 

在我看來(lái),實(shí)際上這個(gè)問題幫助我們理清了值接收者與指針接收者之間的區(qū)別。為了說明這一點(diǎn),下面還有一個(gè)示例,這個(gè)示例與上面兩個(gè)示例沒有關(guān)系。這個(gè)示例使用到了 & 取值符和 %p 格式化輸出變量的地址。

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. type Container struct { 
  6.   i int 
  7.   s string 
  8.  
  9. func (c Container) byValMethod() { 
  10.   fmt.Printf("byValMethod got &c=%p, &(c.s)=%p\n", &c, &(c.s)) 
  11.  
  12. func (c *Container) byPtrMethod() { 
  13.   fmt.Printf("byPtrMethod got &c=%p, &(c.s)=%p\n", c, &(c.s)) 
  14.  
  15. func main() { 
  16.   var c Container 
  17.   fmt.Printf("in main &c=%p, &(c.s)=%p\n", &c, &(c.s)) 
  18.  
  19.   c.byValMethod() 
  20.   c.byPtrMethod() 

執(zhí)行代碼后輸出(如果在你的機(jī)器上執(zhí)行,輸出的地址可能不同,但是這不影響說明問題):

  1. in main &c=0xc00000a060, &(c.s)=0xc00000a068 
  2. byValMethod got &c=0xc00000a080, &(c.s)=0xc00000a088 
  3. byPtrMethod got &c=0xc00000a060, &(c.s)=0xc00000a068 

main() 函數(shù)里創(chuàng)建了 Container 變量 c,并且輸出它的地址和它的成員 s 的地址,接著調(diào)用了 Container 的兩個(gè)方法。byValMethod() 是值接受者,因?yàn)槭窃档目截愃写蛴〉牡刂凡灰粯?。另一方面,byPtrMethod() 是指針接收者,輸出的地址與 main() 函數(shù)輸出的地址一致,因?yàn)檎{(diào)用時(shí)獲取的是 c 實(shí)際的地址,而不是副本。

參考資料

[1]例子: https://github.com/eliben/code-for-blog/tree/master/2018/go-copying-mutex

[2]官方指南: https://golang.org/doc/faq#methods_on_values_or_pointers

[3]Go 文檔: https://golang.org/src/sync/mutex.go

 

責(zé)任編輯:武曉燕 來(lái)源: Golang來(lái)啦
相關(guān)推薦

2021-06-08 07:45:44

Go語(yǔ)言優(yōu)化

2021-07-08 23:53:44

Go語(yǔ)言拷貝

2021-08-02 22:31:24

Go語(yǔ)言Append

2021-01-08 06:15:09

深拷貝淺拷貝寫時(shí)拷貝

2022-06-05 23:30:25

AES加密算法

2024-10-28 00:40:49

Go語(yǔ)法版本

2018-03-12 22:13:46

GO語(yǔ)言編程軟件

2022-07-04 14:41:31

Go 語(yǔ)言變長(zhǎng)參數(shù)變長(zhǎng)參數(shù)函數(shù)

2022-04-13 08:20:32

DockerGo項(xiàng)目

2024-02-06 17:57:06

Go語(yǔ)言任務(wù)

2020-08-12 08:56:30

代碼凱撒密碼函數(shù)

2023-02-13 00:24:37

Go語(yǔ)言日志庫(kù)

2024-01-02 10:38:22

Go語(yǔ)言數(shù)組

2022-07-03 23:07:48

Go語(yǔ)言參數(shù)

2024-04-01 00:02:56

Go語(yǔ)言代碼

2021-12-06 10:22:47

切片拷貝Python

2023-10-09 07:14:42

panicGo語(yǔ)言

2013-06-25 09:52:32

GoGo語(yǔ)言Go編程

2024-05-10 08:36:40

Go語(yǔ)言對(duì)象

2023-07-03 00:44:26

Go語(yǔ)言MySQL
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)