成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

在 Go 語言中,如何正確的使用并發

開發 前端 后端
Go不可能保護你,但是并不意味著你不能采取措施保護自己。在寫代碼過程中通過使用一些Go提供的原語,可最小化相關的搶占式調度產生的異常行為。

Glyph Lefkowitz最近寫了一篇啟蒙文章,其中他詳細的說明了一些關于開發高并發軟件的挑戰,如果你開發軟件但是沒有閱讀這篇問題,那么我建議你閱讀一篇。這是一篇非常好的文章,現代軟件工程應該擁有的豐富智慧。

從多個花絮中提取,但是如果我斗膽提出主要觀點的總結,其內容就是:搶占式多任務和一般共享狀態結合導致軟件開發過程不可管理的復雜性, 開發人員可能更喜歡保持自己的一些理智以此避免這種不可管理的復雜性。搶占式調度對于哪些真正的并行任務是好的,但是當可變狀態通過多并發線程共享時,明確的多任務合作更招人喜歡 。

盡管合作多任務,你的代碼仍有可能是復雜的,它只是有機會保持可管理下一定的復雜性。當控制轉移是明確一個代碼閱讀者至少有一些可見的跡象表明事情可能脫離正軌。沒有明確標記每個新階段是潛在的地雷:“如果這個操作不是原子操作,最后出現什么情況?”那么在每個命令之間的空間變成無盡的空間黑洞,可怕的Heisenbugs出現

在過去的一年多,盡管在Heka上的工作(一個高性能數據、日志和指標處理引擎)已大多數使用GO語言開發。Go的亮點之一就是語言本身有一些非常有用的并發原語。但是Go的并發性能怎么樣,需要通過支持本地推理的鼓勵代碼鏡頭觀察。

并非事實都是好的。所有的Goroutine訪問相同的共享內存空間,狀態默認可變,但是Go的調度程序不保證在上下文選擇過程中的準確性。在單核設置中,Go的運行時間進入“隱式協同工作”一類, 在Glyph中經常提到的異步程序模型列表選擇4。 當Goroutine能夠在多核系統中并行運行,世事難料。

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

  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 

這明顯的是不安全的,如果從多個goroutine中調用的話,因為它們可能并發的從存款調度中得到相同的結果,然后一起請求更多的已取消調用的存款變量。最好是代碼中危險部分不會被多goroutine執行。在此一種方式實現了該功能:

  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 

這里有更多代碼,但是我們通過實現一個微不足道的事件循環消除并發問題。當代碼首次執行時,它激活一個goroutine運行循環。轉發請求為了此目的而傳遞入一個新創建的通道。結果經由一個錯誤通道返回到循環外部。因為通道不是緩沖的,它們加鎖,并且通過Transfer函數無論多個并發的轉發請求怎么進,它們都將通過單一的運行事件循環被持續的服務。

上面的代碼看起來有點別扭,也許吧. 對于這樣一個簡單的場景一個互斥鎖(mutex)也許會是一個更好的選擇,但是我正要嘗試去證明的是可以向一個go例程應用隔離狀態操作. 即使稍稍有點尷尬,但是對于大多數需求而言它的表現已經足夠好了,并且它工作起來,甚至使用了最簡單的賬號結構實現:

  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 

不過如此笨拙的賬戶實現看起來會有點天真. 通過不讓任何大于當前平衡的撤回操作執行,從而讓賬戶結構自身提供一些保護也許更起作用。那如果我們把撤回函數變成下面這個樣子會怎么樣呢?

  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 

不幸的是,這個代碼患有和我們原來的 Transfer 實現相同的問題。并發執行或不幸的上下文切換意味著我們可能以負平衡結束。幸運的是,內部的事件循環理念應用在這里同樣很好,甚至更好,因為事件循環 goroutine 可以與每個個人賬戶結構實例很好的耦合。這里有一個例子說明這一點:

  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.     } 

這個API略有不同,Deposit 和 Withdraw 方法現在都返回了錯誤。它們并非直接處理它們的請求,而是把賬戶余額的調整量放入 deltaChan,在 run 方法運行時的事件循環中訪問 deltaChan。同樣的,Balance 方法通過阻塞不斷地在事件循環中請求數據,直到它通過 balanceChan 接收到一個值。

須注意的要點是上述的代碼,所有對結構內部數據值得直接訪問和修改都是有事件循環觸發的 *within* 代碼來完成的。如果公共 API 調用表現良好并且只使用給出的渠道同數據進行交互的話, 那么不管對公共方法進行多少并發的調用,我們都知道在任意給定的時間只會有它們之中的一個方法得到處理。我們的時間循環代碼推理起來更加容易了很多。

該模式的核心是 Heke 的設計. 當Heka啟動時,它會讀取配置文件并且在它自己的go例程中啟動每一個插件. 隨著時鐘信號、關閉通知和其它控制信號,數據經由通道被送入插件中. 這樣就鼓勵了插件作者使用一種想上述事例那樣的 事件循環類型的架構 來實現插件的功能.

再次,GO不會保護你自己. 寫一個同其內部數據管理和主題有爭議的條件保持松耦合的Heka插件(或者任何架構)是完全可能的。但是有一些需要注意的小地方,還有Go的爭議探測器的自由應用程序,你可以編寫的代碼其行為可以預測,甚至在搶占式調度的門面代碼中。

英文原文:Sane Concurrency with Go

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

責任編輯:林師授 來源: 開源中國社區編譯
相關推薦

2023-12-21 07:09:32

Go語言任務

2021-07-15 23:18:48

Go語言并發

2024-05-10 08:36:40

Go語言對象

2023-01-30 15:41:10

Channel控制并發

2024-04-01 00:02:56

Go語言代碼

2022-11-03 20:38:01

CMD命令Go

2025-05-30 01:55:00

go語言Redis

2024-04-07 11:33:02

Go逃逸分析

2016-02-22 15:02:57

GoRedis連接池

2013-06-25 09:52:32

GoGo語言Go編程

2023-10-09 07:14:42

panicGo語言

2025-02-13 09:02:04

2020-08-12 08:51:19

Go語言Concurrency后臺

2025-04-02 05:23:00

GoChannel數據

2011-05-25 13:22:05

PHPJSON

2023-11-30 08:09:02

Go語言

2022-07-19 12:25:29

Go

2023-07-29 15:03:29

2021-06-08 07:45:44

Go語言優化

2024-01-07 23:11:16

defer?Go語言
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 中文字幕一区二区三区精彩视频 | 亚洲人成网站777色婷婷 | 国产精品久久久久久一区二区三区 | 国产一区二区三区在线免费 | 日本成人在线网址 | 欧美啪啪 | 亚洲午夜精品一区二区三区他趣 | 国产一区二区免费在线 | cao在线| 国产xxxx岁13xxxxhd | 视频一区在线 | 精品久久一区 | 国产韩国精品一区二区三区 | 国产精品明星裸体写真集 | 国产免费观看久久黄av片涩av | 日韩欧美三级在线 | 日韩成人免费视频 | 干干天天 | 免费在线一区二区 | 男女羞羞视频大全 | 国产精品夜夜春夜夜爽久久电影 | 中文字幕视频在线观看免费 | 久久久999国产精品 中文字幕在线精品 | 成人精品一区亚洲午夜久久久 | 亚洲二区在线观看 | 婷婷综合色 | 天天干天天爱天天操 | 男女羞羞在线观看 | 国产免费一区二区三区 | 亚洲欧美在线一区 | 成人在线视频一区 | 亚洲国产成人精品女人久久久 | 国产精品欧美一区二区三区不卡 | 亚洲高清视频一区二区 | 黄网站免费在线 | 国内自拍偷拍视频 | 6996成人影院网在线播放 | 一区二区电影 | 天天干国产 | 亚洲精品视频在线 | 91精品午夜窝窝看片 |