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

在 Go 語(yǔ)言中,如何正確的使用并發(fā)

開(kāi)發(fā) 前端 后端
Go不可能保護(hù)你,但是并不意味著你不能采取措施保護(hù)自己。在寫(xiě)代碼過(guò)程中通過(guò)使用一些Go提供的原語(yǔ),可最小化相關(guān)的搶占式調(diào)度產(chǎn)生的異常行為。

Glyph Lefkowitz最近寫(xiě)了一篇啟蒙文章,其中他詳細(xì)的說(shuō)明了一些關(guān)于開(kāi)發(fā)高并發(fā)軟件的挑戰(zhàn),如果你開(kāi)發(fā)軟件但是沒(méi)有閱讀這篇問(wèn)題,那么我建議你閱讀一篇。這是一篇非常好的文章,現(xiàn)代軟件工程應(yīng)該擁有的豐富智慧。

從多個(gè)花絮中提取,但是如果我斗膽提出主要觀點(diǎn)的總結(jié),其內(nèi)容就是:搶占式多任務(wù)和一般共享狀態(tài)結(jié)合導(dǎo)致軟件開(kāi)發(fā)過(guò)程不可管理的復(fù)雜性, 開(kāi)發(fā)人員可能更喜歡保持自己的一些理智以此避免這種不可管理的復(fù)雜性。搶占式調(diào)度對(duì)于哪些真正的并行任務(wù)是好的,但是當(dāng)可變狀態(tài)通過(guò)多并發(fā)線程共享時(shí),明確的多任務(wù)合作更招人喜歡 。

盡管合作多任務(wù),你的代碼仍有可能是復(fù)雜的,它只是有機(jī)會(huì)保持可管理下一定的復(fù)雜性。當(dāng)控制轉(zhuǎn)移是明確一個(gè)代碼閱讀者至少有一些可見(jiàn)的跡象表明事情可能脫離正軌。沒(méi)有明確標(biāo)記每個(gè)新階段是潛在的地雷:“如果這個(gè)操作不是原子操作,最后出現(xiàn)什么情況?”那么在每個(gè)命令之間的空間變成無(wú)盡的空間黑洞,可怕的Heisenbugs出現(xiàn)

在過(guò)去的一年多,盡管在Heka上的工作(一個(gè)高性能數(shù)據(jù)、日志和指標(biāo)處理引擎)已大多數(shù)使用GO語(yǔ)言開(kāi)發(fā)。Go的亮點(diǎn)之一就是語(yǔ)言本身有一些非常有用的并發(fā)原語(yǔ)。但是Go的并發(fā)性能怎么樣,需要通過(guò)支持本地推理的鼓勵(lì)代碼鏡頭觀察。

并非事實(shí)都是好的。所有的Goroutine訪問(wèn)相同的共享內(nèi)存空間,狀態(tài)默認(rèn)可變,但是Go的調(diào)度程序不保證在上下文選擇過(guò)程中的準(zhǔn)確性。在單核設(shè)置中,Go的運(yùn)行時(shí)間進(jìn)入“隱式協(xié)同工作”一類(lèi), 在Glyph中經(jīng)常提到的異步程序模型列表選擇4。 當(dāng)Goroutine能夠在多核系統(tǒng)中并行運(yùn)行,世事難料。

Go不可能保護(hù)你,但是并不意味著你不能采取措施保護(hù)自己。在寫(xiě)代碼過(guò)程中通過(guò)使用一些Go提供的原語(yǔ),可最小化相關(guān)的搶占式調(diào)度產(chǎn)生的異常行為。請(qǐng)看下面Glyph示例“賬號(hào)轉(zhuǎn)換”代碼段中Go接口(忽略哪些不易于最終存儲(chǔ)定點(diǎn)小數(shù)的浮點(diǎn)數(shù))

  1. func Transfer(amount float64, payer, payee *Account, 
  2.     server SomeServerType) error { 
  3.  
  4.     if payer.Balance() < amount { 
  5.         return errors.New("Insufficient funds"
  6.     } 
  7.     log.Printf("%s has sufficient funds", payer) 
  8.     payee.Deposit(amount) 
  9.     log.Printf("%s received payment", payee) 
  10.     payer.Withdraw(amount) 
  11.     log.Printf("%s made payment", payer) 
  12.     server.UpdateBalances(payer, payee) // Assume this is magic and always works. 
  13.     return nil 

這明顯的是不安全的,如果從多個(gè)goroutine中調(diào)用的話,因?yàn)樗鼈兛赡懿l(fā)的從存款調(diào)度中得到相同的結(jié)果,然后一起請(qǐng)求更多的已取消調(diào)用的存款變量。最好是代碼中危險(xiǎn)部分不會(huì)被多goroutine執(zhí)行。在此一種方式實(shí)現(xiàn)了該功能:

  1. type transfer struct { 
  2.     payer *Account 
  3.     payee *Account 
  4.     amount float64 
  5.  
  6. var xferChan = make(chan *transfer) 
  7. var errChan = make(chan error) 
  8. func init() { 
  9.     go transferLoop() 
  10.  
  11. func transferLoop() { 
  12.     for xfer := range xferChan { 
  13.         if xfer.payer.Balance < xfer.amount { 
  14.             errChan <- errors.New("Insufficient funds"
  15.             continue 
  16.         } 
  17.         log.Printf("%s has sufficient funds", xfer.payer) 
  18.         xfer.payee.Deposit(xfer.amount) 
  19.         log.Printf("%s received payment", xfer.payee) 
  20.         xfer.payer.Withdraw(xfer.amount) 
  21.         log.Printf("%s made payment", xfer.payer) 
  22.         errChan <- nil 
  23.     } 
  24.  
  25. func Transfer(amount float64, payer, payee *Account, 
  26.     server SomeServerType) error { 
  27.  
  28.     xfer := &transfer{ 
  29.         payer: payer, 
  30.         payee: payee, 
  31.         amount: amount, 
  32.     } 
  33.  
  34.     xferChan <- xfer 
  35.     err := <-errChan 
  36.     if err == nil  { 
  37.         server.UpdateBalances(payer, payee) // Still magic. 
  38.     } 
  39.     return err 

這里有更多代碼,但是我們通過(guò)實(shí)現(xiàn)一個(gè)微不足道的事件循環(huán)消除并發(fā)問(wèn)題。當(dāng)代碼首次執(zhí)行時(shí),它激活一個(gè)goroutine運(yùn)行循環(huán)。轉(zhuǎn)發(fā)請(qǐng)求為了此目的而傳遞入一個(gè)新創(chuàng)建的通道。結(jié)果經(jīng)由一個(gè)錯(cuò)誤通道返回到循環(huán)外部。因?yàn)橥ǖ啦皇蔷彌_的,它們加鎖,并且通過(guò)Transfer函數(shù)無(wú)論多個(gè)并發(fā)的轉(zhuǎn)發(fā)請(qǐng)求怎么進(jìn),它們都將通過(guò)單一的運(yùn)行事件循環(huán)被持續(xù)的服務(wù)。

上面的代碼看起來(lái)有點(diǎn)別扭,也許吧. 對(duì)于這樣一個(gè)簡(jiǎn)單的場(chǎng)景一個(gè)互斥鎖(mutex)也許會(huì)是一個(gè)更好的選擇,但是我正要嘗試去證明的是可以向一個(gè)go例程應(yīng)用隔離狀態(tài)操作. 即使稍稍有點(diǎn)尷尬,但是對(duì)于大多數(shù)需求而言它的表現(xiàn)已經(jīng)足夠好了,并且它工作起來(lái),甚至使用了最簡(jiǎn)單的賬號(hào)結(jié)構(gòu)實(shí)現(xiàn):

  1. type Account struct { 
  2.     balance float64 
  3.  
  4. func (a *Account) Balance() float64 { 
  5.     return a.balance 
  6.  
  7. func (a *Account) Deposit(amount float64) { 
  8.     log.Printf("depositing: %f", amount) 
  9.     a.balance += amount 
  10.  
  11. func (a *Account) Withdraw(amount float64) { 
  12.     log.Printf("withdrawing: %f", amount) 
  13.     a.balance -= amount 

不過(guò)如此笨拙的賬戶實(shí)現(xiàn)看起來(lái)會(huì)有點(diǎn)天真. 通過(guò)不讓任何大于當(dāng)前平衡的撤回操作執(zhí)行,從而讓賬戶結(jié)構(gòu)自身提供一些保護(hù)也許更起作用。那如果我們把撤回函數(shù)變成下面這個(gè)樣子會(huì)怎么樣呢?

  1. func (a *Account) Withdraw(amount float64) { 
  2.     if amount > a.balance { 
  3.         log.Println("Insufficient funds"
  4.         return 
  5.     } 
  6.     log.Printf("withdrawing: %f", amount) 
  7.     a.balance -= amount 

不幸的是,這個(gè)代碼患有和我們?cè)瓉?lái)的 Transfer 實(shí)現(xiàn)相同的問(wèn)題。并發(fā)執(zhí)行或不幸的上下文切換意味著我們可能以負(fù)平衡結(jié)束。幸運(yùn)的是,內(nèi)部的事件循環(huán)理念應(yīng)用在這里同樣很好,甚至更好,因?yàn)槭录h(huán) goroutine 可以與每個(gè)個(gè)人賬戶結(jié)構(gòu)實(shí)例很好的耦合。這里有一個(gè)例子說(shuō)明這一點(diǎn):

  1. type Account struct { 
  2.     balance float64 
  3.     deltaChan chan float64 
  4.     balanceChan chan float64 
  5.     errChan chan error 
  1. func NewAccount(balance float64) (a *Account) { 
  2.     a = &Account{ 
  3.         balance:     balance, 
  4.         deltaChan:   make(chan float64), 
  5.         balanceChan: make(chan float64), 
  6.         errChan:     make(chan error), 
  7.     } 
  8.     go a.run() 
  9.     return 
  10.  
  11. func (a *Account) Balance() float64 { 
  12.     return <-a.balanceChan 
  13.  
  14. func (a *Account) Deposit(amount float64) error { 
  15.     a.deltaChan <- amount 
  16.     return <-a.errChan 
  17.  
  18. func (a *Account) Withdraw(amount float64) error { 
  19.     a.deltaChan <- -amount 
  20.     return <-a.errChan 
  21.  
  22. func (a *Account) applyDelta(amount float64) error { 
  23.     newBalance := a.balance + amount 
  24.     if newBalance < 0 { 
  25.         return errors.New("Insufficient funds"
  26.     } 
  27.     a.balance = newBalance 
  28.     return nil 
  29.  
  30. func (a *Account) run() { 
  31.     var delta float64 
  32.     for { 
  33.         select { 
  34.         case delta = <-a.deltaChan: 
  35.             a.errChan <- a.applyDelta(delta) 
  36.         case a.balanceChan <- a.balance: 
  37.             // Do nothing, we've accomplished our goal w/ the channel put. 
  38.         } 
  39.     } 

這個(gè)API略有不同,Deposit 和 Withdraw 方法現(xiàn)在都返回了錯(cuò)誤。它們并非直接處理它們的請(qǐng)求,而是把賬戶余額的調(diào)整量放入 deltaChan,在 run 方法運(yùn)行時(shí)的事件循環(huán)中訪問(wèn) deltaChan。同樣的,Balance 方法通過(guò)阻塞不斷地在事件循環(huán)中請(qǐng)求數(shù)據(jù),直到它通過(guò) balanceChan 接收到一個(gè)值。

須注意的要點(diǎn)是上述的代碼,所有對(duì)結(jié)構(gòu)內(nèi)部數(shù)據(jù)值得直接訪問(wèn)和修改都是有事件循環(huán)觸發(fā)的 *within* 代碼來(lái)完成的。如果公共 API 調(diào)用表現(xiàn)良好并且只使用給出的渠道同數(shù)據(jù)進(jìn)行交互的話, 那么不管對(duì)公共方法進(jìn)行多少并發(fā)的調(diào)用,我們都知道在任意給定的時(shí)間只會(huì)有它們之中的一個(gè)方法得到處理。我們的時(shí)間循環(huán)代碼推理起來(lái)更加容易了很多。

該模式的核心是 Heke 的設(shè)計(jì). 當(dāng)Heka啟動(dòng)時(shí),它會(huì)讀取配置文件并且在它自己的go例程中啟動(dòng)每一個(gè)插件. 隨著時(shí)鐘信號(hào)、關(guān)閉通知和其它控制信號(hào),數(shù)據(jù)經(jīng)由通道被送入插件中. 這樣就鼓勵(lì)了插件作者使用一種想上述事例那樣的 事件循環(huán)類(lèi)型的架構(gòu) 來(lái)實(shí)現(xiàn)插件的功能.

再次,GO不會(huì)保護(hù)你自己. 寫(xiě)一個(gè)同其內(nèi)部數(shù)據(jù)管理和主題有爭(zhēng)議的條件保持松耦合的Heka插件(或者任何架構(gòu))是完全可能的。但是有一些需要注意的小地方,還有Go的爭(zhēng)議探測(cè)器的自由應(yīng)用程序,你可以編寫(xiě)的代碼其行為可以預(yù)測(cè),甚至在搶占式調(diào)度的門(mén)面代碼中。

英文原文:Sane Concurrency with Go

譯文鏈接:http://www.oschina.net/translate/sane-concurrency-with-go

責(zé)任編輯:林師授 來(lái)源: 開(kāi)源中國(guó)社區(qū)編譯
相關(guān)推薦

2023-12-21 07:09:32

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

2021-07-15 23:18:48

Go語(yǔ)言并發(fā)

2024-05-10 08:36:40

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

2023-01-30 15:41:10

Channel控制并發(fā)

2024-04-01 00:02:56

Go語(yǔ)言代碼

2022-11-03 20:38:01

CMD命令Go

2024-04-07 11:33:02

Go逃逸分析

2013-06-25 09:52:32

GoGo語(yǔ)言Go編程

2023-10-09 07:14:42

panicGo語(yǔ)言

2016-02-22 15:02:57

GoRedis連接池

2025-02-13 09:02:04

2020-08-12 08:51:19

Go語(yǔ)言Concurrency后臺(tái)

2025-04-02 05:23:00

GoChannel數(shù)據(jù)

2022-07-19 12:25:29

Go

2023-07-29 15:03:29

2023-11-30 08:09:02

Go語(yǔ)言

2021-06-08 07:45:44

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

2011-05-25 13:22:05

PHPJSON

2024-01-07 23:11:16

defer?Go語(yǔ)言

2023-01-12 08:52:50

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

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