欧美阿v视频在线大全_亚洲欧美中文日韩V在线观看_www性欧美日韩欧美91_亚洲欧美日韩久久精品

主頁 > 知識庫 > Go語言并發模型的2種編程方案

Go語言并發模型的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,因為這樣代碼更加清晰。

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

您可能感興趣的文章:
  • golang gin 框架 異步同步 goroutine 并發操作
  • Golang 探索對Goroutine的控制方法(詳解)
  • 關于Golang中for-loop與goroutine的問題詳解
  • go語言執行等待直到后臺goroutine執行完成實例分析
  • Go語言輕量級線程Goroutine用法實例
  • go獲取協程(goroutine)號的實例
  • 分析Go語言中CSP并發模型與Goroutine的基本使用

標簽:九江 運城 本溪 晉城 湘潭 喀什 楚雄 深圳

巨人網絡通訊聲明:本文標題《Go語言并發模型的2種編程方案》,本文關鍵詞  語言,并發,模型,的,2種,;如發現本文內容存在版權問題,煩請提供相關信息告之我們,我們將及時溝通與處理。本站內容系統采集于網絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《Go語言并發模型的2種編程方案》相關的同類信息!
  • 本頁收集關于Go語言并發模型的2種編程方案的相關信息資訊供網民參考!
  • 推薦文章
    欧美阿v视频在线大全_亚洲欧美中文日韩V在线观看_www性欧美日韩欧美91_亚洲欧美日韩久久精品
  • <rt id="w000q"><acronym id="w000q"></acronym></rt>
  • <abbr id="w000q"></abbr>
    <rt id="w000q"></rt>
    av高清不卡在线| 欧美精品久久一区| 日本一区二区三区四区在线视频 | 欧美图片一区二区| 在线综合+亚洲+欧美中文字幕| 一区二区成人在线视频| 97超碰欧美中文字幕| 色综合久久久久综合| 中文字幕一区av| aa级大片欧美| 在线观看精品一区| 一区二区三区中文字幕电影| 一卡二卡三卡四卡五卡| 欧美日韩一级视频| 午夜精品一区在线观看| 日韩欧美国产精品| 亚洲一区二区四区蜜桃| 波多野结衣爱爱视频| 久久精品噜噜噜成人av农村| 国产精品一级黄片| 日韩欧美国产1| 久久精品国产亚洲5555| 久久久久无码精品国产sm果冻| 精品久久免费看| 国产麻豆精品久久一二三| 亚洲精品自拍视频在线观看| 国产精品久久三区| 91啪九色porn原创视频在线观看| 欧美三级蜜桃2在线观看| 亚洲成人动漫在线免费观看| 久久偷拍免费视频| 久久伊99综合婷婷久久伊| 国产a区久久久| 欧亚洲嫩模精品一区三区| 五月天网站亚洲| 亚洲女优在线观看| 国产精品卡一卡二卡三| 国产成人精品一区二区三区在线观看| 6080yy午夜一二三区久久| 麻豆成人免费电影| 亚洲一级生活片| 一二三四社区欧美黄| 国产精品无码一区二区三区免费 | 中文字幕一区二区人妻电影丶| 日韩欧美国产系列| 国产成人精品影视| 欧美片网站yy| 国产一区二区精品在线观看| 一本色道久久综合亚洲91| 亚洲国产精品久久艾草纯爱| 美女脱光内衣内裤| 亚洲天堂中文字幕| 亚洲av片不卡无码久久| 国产精品女主播av| www.四虎在线| 久久精品一区蜜桃臀影院| gogogo免费视频观看亚洲一| 91精品国产福利在线观看| 国产一区二区三区在线观看免费| 欧洲精品一区二区| 久久97超碰国产精品超碰| 色综合久久久网| 另类小说欧美激情| 色88888久久久久久影院野外 | 色爱区综合激月婷婷| 免费看精品久久片| 色综合久久久久网| 久久激情五月婷婷| 欧美在线一区二区| 寂寞少妇一区二区三区| 欧美性猛交xxxx乱大交退制版| 欧美日韩三级视频| 精品一区二区三区久久久| 在线精品观看国产| 国产一区二区视频在线播放| 在线播放一区二区三区| 成人一级片在线观看| 91精品欧美久久久久久动漫| 成人免费精品视频| 欧美成人激情免费网| 91麻豆免费看| 日本一区二区三区四区| 免费在线观看成年人视频| 亚洲欧美视频在线观看| av免费播放网站| 亚洲chinese男男1069| 18岁成人毛片| 久久99精品国产91久久来源| 在线电影欧美成精品| 成人av集中营| 欧美韩日一区二区三区| 人人妻人人澡人人爽人人精品| 亚洲人成7777| 一二三四国产精品| 欧美aⅴ一区二区三区视频| 欧美午夜影院一区| 成人国产一区二区三区精品| wwwwww.欧美系列| 亚洲成人av免费在线观看| 亚洲一区中文日韩| 色综合咪咪久久| 国产成人精品1024| 国产亚洲综合色| 熟女高潮一区二区三区| 天天综合天天做天天综合| 欧美午夜精品一区二区三区| 丁香婷婷综合色啪| 欧美韩国一区二区| ass极品国模人体欣赏| 毛片av一区二区| 91精品国产品国语在线不卡| 中文字幕99页| 一区二区不卡在线播放 | 日韩欧美国产午夜精品| 亚洲天堂av网站| 亚洲一区二区三区四区的| 在线观看精品一区| 91网上在线视频| 亚洲欧美成人一区二区三区| 色猫猫国产区一区二在线视频| 国产aⅴ综合色| 国产精品国产三级国产普通话99| 国产成人在线网址| 国产尤物一区二区在线| 久久精品在线免费观看| 萌白酱视频在线| 国产精品18久久久久久久久久久久| 久久久久久久久97黄色工厂| 久久久精品成人| 国产精品一区2区| 日本一区二区成人| 伊人在线视频观看| 成人av电影在线网| 一区二区激情小说| 欧美丰满高潮xxxx喷水动漫| 欧美日韩一区二区三区四区五区六区| 亚洲素人一区二区| 欧洲激情一区二区| 国产原创剧情av| 日韩av电影免费观看高清完整版 | 欧美午夜在线观看| 国产伦精品一区三区精东| 首页欧美精品中文字幕| 日韩欧美中文字幕制服| 丰腴饱满的极品熟妇| 韩国av一区二区三区在线观看| 国产女主播在线一区二区| 日韩欧美123区| 91污片在线观看| 天天综合网 天天综合色| 欧美本精品男人aⅴ天堂| 成人免费视频入口| 成人aaaa免费全部观看| 亚洲最大成人综合| 日韩三级伦理片妻子的秘密按摩| 国产综合精品久久久久成人av| 国产精品1区2区| 亚洲精品国产一区二区三区四区在线| 欧美日韩亚洲综合在线 | 波兰性xxxxx极品hd| 成人h精品动漫一区二区三区| 亚洲精品乱码久久久久| 欧美一区二区三区电影| 国产三级av在线播放| 成人精品一区二区三区中文字幕| 有坂深雪av一区二区精品| 日韩欧美亚洲另类制服综合在线| 免费看91的网站| 国产成人h网站| 亚洲电影第三页| 26uuu亚洲综合色欧美| 久久久久亚洲av无码专区体验| 亚洲国产精品第一页| 狠狠网亚洲精品| 亚洲女厕所小便bbb| 日韩精品一区二区三区在线播放 | 国产精品久久久久久久久动漫 | 亚洲国产精品自拍| 欧美xxxxx牲另类人与| 超碰在线国产97| 在线精品一区二区三区| 国产不卡在线一区| 亚洲高清不卡在线观看| 国产午夜亚洲精品不卡| 欧美日韩精品一区二区三区四区 | 久久久久久久久一| 99视频一区二区| 午夜精品福利一区二区三区av | 欧美天天综合网| 精品无码人妻一区二区免费蜜桃 | 国产精品国产自产拍在线| 欧美一区二区三区影视| 欧美日韩黄色网| 国产黄色网址在线观看| 99久久精品国产毛片| 久久国产乱子精品免费女| 亚洲综合区在线| 中文字幕精品一区二区精品绿巨人| 欧美精品色一区二区三区| 日韩三级在线观看视频|