亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Golang > 正文

Go語言并發模型的2種編程方案

2020-04-01 19:24:13
字體:
來源:轉載
供稿:網友
這篇文章主要介紹了Go語言并發模型的2種編程方案,本文給出共享內存和通過通信的2種解決方案,并給出了實現代碼,需要的朋友可以參考下
 

概述

我一直在找一種好的方法來解釋 go 語言的并發模型:

不要通過共享內存來通信,相反,應該通過通信來共享內存

但是沒有發現一個好的解釋來滿足我下面的需求:

1.通過一個例子來說明最初的問題
2.提供一個共享內存的解決方案
3.提供一個通過通信的解決方案

這篇文章我就從這三個方面來做出解釋。

讀過這篇文章后你應該會了解通過通信來共享內存的模型,以及它和通過共享內存來通信的區別,你還將看到如何分別通過這兩種模型來解決訪問和修改共享資源的問題。

前提

設想一下我們要訪問一個銀行賬號:

復制代碼代碼如下:

type Account interface {
  Withdraw(uint)
  Deposit(uint)
  Balance() int
}

 

type Bank struct {
  account Account
}

func NewBank(account Account) *Bank {
  return &Bank{account: account}
}

func (bank *Bank) Withdraw(amount uint, actor_name string) {
  fmt.Println("[-]", amount, actor_name)
  bank.account.Withdraw(amount)
}

func (bank *Bank) Deposit(amount uint, actor_name string) {
  fmt.Println("[+]", amount, actor_name)
  bank.account.Deposit(amount)
}

func (bank *Bank) Balance() int {
  return bank.account.Balance()
}

 

因為 Account 是一個接口,所以我們提供一個簡單的實現:

 

復制代碼代碼如下:

type SimpleAccount struct{
  balance int
}

 

func NewSimpleAccount(balance int) *SimpleAccount {
  return &SimpleAccount{balance: balance}
}

func (acc *SimpleAccount) Deposit(amount uint) {
  acc.setBalance(acc.balance + int(amount))
}

func (acc *SimpleAccount) Withdraw(amount uint) {
  if acc.balance >= int(mount) {
    acc.setBalance(acc.balance - int(amount))
  } else {
    panic("杰克窮死")
  }
}

func (acc *SimpleAccount) Balance() int {
  return acc.balance
}

func (acc *SimpleAccount) setBalance(balance int) {
  acc.add_some_latency()  //增加一個延時函數,方便演示
  acc.balance = balance
}

func (acc *SimpleAccount) add_some_latency() {
  <-time.After(time.Duration(rand.Intn(100)) * time.Millisecond)
}

 

你可能注意到了 balance 沒有被直接修改,而是被放到了 setBalance 方法里進行修改。這樣設計是為了更好的描述問題。稍后我會做出解釋。

把上面所有部分弄好以后我們就可以像下面這樣使用它啦:

復制代碼代碼如下:

func main() {
  balance := 80
  b := NewBank(bank.NewSimpleAccount(balance))
  
  fmt.Println("初始化余額", b.Balance())
  
  b.Withdraw(30, "馬伊琍")
  
  fmt.Println("-----------------")
  fmt.Println("剩余余額", b.Balance())
}

 

運行上面的代碼會輸出:

 

復制代碼代碼如下:

初始化余額 80
[-] 30 馬伊琍
-----------------
剩余余額 50

 

沒錯!

不錯在現實生活中,一個銀行賬號可以有很多個附屬卡,不同的附屬卡都可以對同一個賬號進行存取錢,所以我們來修改一下代碼:

復制代碼代碼如下:

func main() {
  balance := 80
  b := NewBank(bank.NewSimpleAccount(balance))
  
  fmt.Println("初始化余額", b.Balance())
  
  done := make(chan bool)
  
  go func() { b.Withdraw(30, "馬伊琍"); done <- true }()
  go func() { b.Withdraw(10, "姚笛"); done <- true }()
  
  //等待 goroutine 執行完成
  <-done
  <-done
  
  fmt.Println("-----------------")
  fmt.Println("剩余余額", b.Balance())
}

 

這兒兩個附屬卡并發的從賬號里取錢,來看看輸出結果:

復制代碼代碼如下:

初始化余額 80
[-] 30 馬伊琍
[-] 10 姚笛
-----------------
剩余余額 70

 

這下把文章高興壞了:)

結果當然是錯誤的,剩余余額應該是40而不是70,那么讓我們看看到底哪兒出問題了。

問題

當并發訪問共享資源時,無效狀態有很大可能會發生。

在我們的例子中,當兩個附屬卡同一時刻從同一個賬號取錢后,我們最后得到銀行賬號(即共享資源)錯誤的剩余余額(即無效狀態)。

我們來看一下執行時候的情況:

 

復制代碼代碼如下:

     處理情況
             --------------
             _馬伊琍_|_姚笛_
 1. 獲取余額     80  |  80
 2. 取錢       -30  | -10
 3. 當前剩余     50  |  70
                ... | ...
 4. 設置余額     50  ?  70  //該先設置哪個好呢?
 5. 后設置的生效了
             --------------
 6. 剩余余額        70

 

上面 ... 的地方描述了我們 add_some_latency 實現的延時狀況,現實世界經常發生延遲情況。所以最后的剩余余額就由最后設置余額的那個附屬卡決定。

解決辦法

我們通過兩種方法來解決這個問題:

1.共享內存的解決方案
2.通過通信的解決方案

所有的解決方案都是簡單的封裝了一下 SimpleAccount 來實現保護機制。

共享內存的解決方案

又叫 “通過共享內存來通信”。

這種方案暗示了使用鎖機制來預防同時訪問和修改共享資源。鎖告訴其它處理程序這個資源已經被一個處理程序占用了,因此別的處理程序需要排隊直到當前處理程序處理完畢。

讓我們來看看 LockingAccount 是怎么實現的:

 

復制代碼代碼如下:

type LockingAccount struct {
  lock    sync.Mutex
  account *SimpleAccount
}

 

//封裝一下 SimpleAccount
func NewLockingAccount(balance int) *LockingAccount {
  return &LockingAccount{account: NewSimpleAccount(balance)}
}

func (acc *LockingAccount) Deposit(amount uint) {
  acc.lock.Lock()
  defer acc.lock.Unlock()
  acc.account.Deposit(amount)
}

func (acc *LockingAccount) Withdraw(amount uint) {
  acc.lock.Lock()
  defer acc.lock.Unlock()
  acc.account.Withdraw(amount)
}

func (acc *LockingAccount) Balance() int {
  acc.lock.Lock()
  defer acc.lock.Unlock()
  return acc.account.Balance()
}

 

直接明了!注意 lock sync.Lock,lock.Lock(),lock.Unlock()。

這樣每次一個附屬卡訪問銀行賬號(即共享資源),這個附屬卡會自動獲得鎖直到最后操作完畢。

我們的 LockingAccount 像下面這樣使用:

復制代碼代碼如下:

func main() {
  balance := 80
  b := NewBank(bank.NewLockingAccount(balance))
  
  fmt.Println("初始化余額", b.Balance())
  
  done := make(chan bool)
  
  go func() { b.Withdraw(30, "馬伊琍"); done <- true }()
  go func() { b.Withdraw(10, "姚笛"); done <- true }()
  
  //等待 goroutine 執行完成
  <-done
  <-done
  
  fmt.Println("-----------------")
  fmt.Println("剩余余額", b.Balance())
}

 

輸出的結果是:

復制代碼代碼如下:

初始化余額 80
[-] 30 馬伊琍
[-] 10 姚笛
-----------------
剩余余額 40

 

現在結果正確了!

在這個例子中第一個處理程序加鎖后獨享共享資源,其它處理程序只能等待它執行完成。

我們接著看一下執行時的情況,假設馬伊琍先拿到了鎖:

復制代碼代碼如下:

處理過程
                        ________________
                        _馬伊琍_|__姚笛__
        加鎖                   ><
        得到余額            80  |
        取錢               -30  |
        當前余額            50  |
                           ... |
        設置余額            50  |
        解除鎖                 <>
                               |
        當前余額                50
                               |
        加鎖                   ><
        得到余額                |  50
        取錢                    | -10
        當前余額                |  40
                               |  ...
        設置余額                |  40
        解除鎖                  <>
                        ________________
        剩余余額                40

 

現在我們的處理程序在訪問共享資源時相繼的產生了正確的結果。

通過通信的解決方案

又叫 “通過通信來共享內存”。

現在賬號被命名為 ConcurrentAccount,像下面這樣來實現:

復制代碼代碼如下:

type ConcurrentAccount struct {
  account     *SimpleAccount
  deposits    chan uint
  withdrawals chan uint
  balances    chan chan int
}

 

func NewConcurrentAccount(amount int) *ConcurrentAccount{
  acc := &ConcurrentAccount{
    account :    &SimpleAccount{balance: amount},
    deposits:    make(chan uint),
    withdrawals: make(chan uint),
    balances:    make(chan chan int),
  }
  acc.listen()
  
  return acc
}

func (acc *ConcurrentAccount) Balance() int {
  ch := make(chan int)
  acc.balances <- ch
  return <-ch
}

func (acc *ConcurrentAccount) Deposit(amount uint) {
  acc.deposits <- amount
}

func (acc *ConcurrentAccount) Withdraw(amount uint) {
  acc.withdrawals <- amount
}

func (acc *ConcurrentAccount) listen() {
  go func() {
    for {
      select {
      case amnt := <-acc.deposits:
        acc.account.Deposit(amnt)
      case amnt := <-acc.withdrawals:
        acc.account.Withdraw(amnt)
      case ch := <-acc.balances:
        ch <- acc.account.Balance()
      }
    }
  }()
}

 

ConcurrentAccount 同樣封裝了 SimpleAccount ,然后增加了通信通道

調用代碼和加鎖版本的一樣,這里就不寫了,唯一不一樣的就是初始化銀行賬號的時候:

復制代碼代碼如下:

b := NewBank(bank.NewConcurrentAccount(balance))

 

運行產生的結果和加鎖版本一樣:

 

復制代碼代碼如下:

初始化余額 80
[-] 30 馬伊琍
[-] 10 姚笛
-----------------
剩余余額 40

 

讓我們來深入了解一下細節。

通過通信來共享內存是如何工作的

一些基本注意點:

共享資源被封裝在一個控制流程中。

結果就是資源成為了非共享狀態。沒有處理程序能夠直接訪問或者修改資源。你可以看到訪問和修改資源的方法實際上并沒有執行任何改變。

復制代碼代碼如下:

func (acc *ConcurrentAccount) Balance() int {
    ch := make(chan int)
    acc.balances <- ch
    balance := <-ch
    return balance
  }
  func (acc *ConcurrentAccount) Deposit(amount uint) {
    acc.deposits <- amount
  }

 

  func (acc *ConcurrentAccount) Withdraw(amount uint) {
    acc.withdrawals <- amount
  }

 

訪問和修改是通過消息和控制流程通信。

在控制流程中任何訪問和修改的動作都是相繼發生的。

當控制流程接收到訪問或者修改的請求后會立即執行相關動作。讓我們仔細看看這個流程:

 

復制代碼代碼如下:

func (acc *ConcurrentAccount) listen() {
    // 執行控制流程
    go func() {
      for {
        select {
        case amnt := <-acc.deposits:
          acc.account.Deposit(amnt)
        case amnt := <-acc.withdrawals:
          acc.account.Withdraw(amnt)
        case ch := <-acc.balances:
          ch <- acc.account.Balance()
        }
      }
    }()
  }

 

select  不斷地從各個通道中取出消息,每個通道都跟它們所要執行的操作相一致。

重要的一點是:在 select 聲明內部的一切都是相繼執行的(在同一個處理程序中排隊執行)。一次只有一個事件(在通道中接受或者發送)發生,這樣就保證了同步訪問共享資源。

領會這個有一點繞。

讓我們用例子來看看 Balance() 的執行情況:

復制代碼代碼如下:

 一張附屬卡的流程      |   控制流程 
      ----------------------------------------------

 

 1.     b.Balance()         |
 2.             ch -> [acc.balances]-> ch
 3.             <-ch        |  balance = acc.account.Balance()
 4.     return  balance <-[ch]<- balance
 5                          |

 

這兩個流程都干了點什么呢?

附屬卡的流程

1.調用 b.Balance()
2.新建通道 ch,將 ch 通道塞入通道 acc.balances 中與控制流程通信,這樣控制流程也可以通過 ch 來返回余額
3.等待 <-ch 來取得要接受的余額
4.接受余額
5.繼續

控制流程

1.空閑或者處理
2.通過 acc.balances 通道里面的 ch 通道來接受余額請求
3.取得真正的余額值
4.將余額值發送到 ch 通道
5.準備處理下一個請求

控制流程每次只處理一個 事件。這也就是為什么除了描述出來的這些以外,第2-4步沒有別的操作執行。

總結

這篇博客描述了問題以及問題的解決辦法,但那時沒有深入去探究不同解決辦法的優缺點。

其實這篇文章的例子更適合用 mutex,因為這樣代碼更加清晰。

最后,請毫無顧忌的指出我的錯誤!


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品99久久久久久久久久久久| 国产视频亚洲精品| 国产一区二区三区毛片| 欧美精品激情在线| 91丝袜美腿美女视频网站| 国产精品999999| 亚洲综合小说区| 日韩欧美中文免费| 日韩三级影视基地| 亚洲香蕉伊综合在人在线视看| 69精品小视频| 国产精品老牛影院在线观看| 亚洲国产欧美一区二区丝袜黑人| 日本精品免费一区二区三区| 2019日本中文字幕| 国产视频自拍一区| 亚洲午夜性刺激影院| 久久精品在线播放| 久久久99久久精品女同性| 揄拍成人国产精品视频| 日本一区二区在线免费播放| 精品久久久久久久久国产字幕| 成人免费视频网址| 欧美视频不卡中文| 欧美午夜视频在线观看| 美日韩精品视频免费看| 亚洲天堂一区二区三区| 国色天香2019中文字幕在线观看| 欧美一级淫片丝袜脚交| 色香阁99久久精品久久久| 国产69精品久久久| 成人国产在线视频| 亚洲欧美精品一区二区| 国模叶桐国产精品一区| 国产成人精品视| 成人综合网网址| 亚洲精品电影网在线观看| 国模gogo一区二区大胆私拍| 日韩欧美国产黄色| 国产精品老女人精品视频| 欧美在线视频a| 亚洲欧美国产精品va在线观看| 97精品国产97久久久久久免费| 久热爱精品视频线路一| 日韩成人在线视频网站| 欧美高清自拍一区| 午夜精品久久久久久久99热| 中文字幕日韩在线视频| 欧美日本中文字幕| 亚洲成人网在线| 热re91久久精品国99热蜜臀| 欧美国产日产韩国视频| 欧美肥老妇视频| 97国产suv精品一区二区62| 久久久视频精品| 清纯唯美日韩制服另类| 深夜福利亚洲导航| 青青在线视频一区二区三区| 68精品国产免费久久久久久婷婷| 亚洲在线第一页| 亚洲欧美日本伦理| 亚洲高清一二三区| 亚洲女性裸体视频| 精品中文字幕视频| 午夜精品一区二区三区在线视频| 亚洲97在线观看| 国内精品久久久久久影视8| 九九热这里只有在线精品视| 亚洲另类欧美自拍| 欧美插天视频在线播放| 亚洲人免费视频| 久久天天躁狠狠躁夜夜躁| 欧美日韩综合视频网址| 久久色免费在线视频| 中文字幕在线看视频国产欧美| 日产精品99久久久久久| 国产午夜精品视频| 亚洲的天堂在线中文字幕| 国产成人在线一区| 高清一区二区三区四区五区| 日韩高清电影免费观看完整| 国产91免费观看| 91午夜理伦私人影院| 亚洲精品久久久久久久久久久久| 亚洲最大的免费| 91中文字幕在线观看| 久久伊人精品一区二区三区| 日韩中文字幕在线观看| 精品动漫一区二区三区| 日韩成人av网址| 国产视频久久久久久久| 狠狠久久五月精品中文字幕| 国产精品伦子伦免费视频| 欧美一区二区三区图| 97久久超碰福利国产精品…| 欧美xxxwww| 日韩欧美精品免费在线| 午夜精品美女自拍福到在线| 欧美不卡视频一区发布| 欧洲美女7788成人免费视频| 欧美高清在线视频观看不卡| 亚洲精品在线观看www| 日韩av123| 亚洲欧美国产日韩中文字幕| 久久久这里只有精品视频| 日韩精品视频免费专区在线播放| 久久久免费精品| 久久综合伊人77777蜜臀| 国产www精品| 欧美另类老肥妇| 亚洲欧美日韩国产精品| 中文国产成人精品久久一| 国产丝袜一区二区| 亚洲国产天堂网精品网站| 欧美日韩在线视频观看| 久久久女女女女999久久| 国产女人18毛片水18精品| 亚洲人成自拍网站| 成人午夜一级二级三级| 欧美激情小视频| 欧美一区二区三区四区在线| 亚洲国产精彩中文乱码av| 欧美国产第一页| 日韩最新av在线| 这里只有精品久久| 国产精品免费一区| 国产色婷婷国产综合在线理论片a| 欧美国产视频一区二区| 欧美一区二区大胆人体摄影专业网站| 亚洲一区美女视频在线观看免费| 日韩中文理论片| 亚洲成年人在线播放| 欧美日韩亚洲一区二区| 国产亚洲精品久久久| 国产精品美女在线| 国产视频丨精品|在线观看| 国产精品∨欧美精品v日韩精品| 亚洲欧美日韩成人| 国产精品欧美一区二区三区奶水| 亚洲最大福利视频网站| 欧美三级欧美成人高清www| 日韩av有码在线| 国产精品27p| 91精品视频观看| 欧美极品欧美精品欧美视频| 成人黄色短视频在线观看| 国产精品久久久久久亚洲调教| 国产91精品高潮白浆喷水| 国产精品都在这里| 亚洲欧美中文日韩在线v日本| 欧美日韩亚洲激情| 欧美日韩综合视频网址| 日韩欧美在线观看视频| 日韩中文字幕精品视频| 亚洲第一级黄色片| 在线观看欧美成人| 亚洲精品国产精品自产a区红杏吧| 欧美日本中文字幕| 欧美老肥婆性猛交视频| 色综合久久88色综合天天看泰| 少妇久久久久久| 热99精品里视频精品| 欧美激情xxxx性bbbb| 国产成人aa精品一区在线播放|