GoLang之Concurrency再討論
0 goroutine是否併發的問題
GoLang通過go關鍵字實現併發操作(真的併發嗎?),一個最簡單的併發模型:
- package main
- import (
- "fmt"
- "math/rand"
- "time"
- )
- func routine(name string, delay time.Duration) {
- t0 := time.Now()
- fmt.Println(name, " start at ", t0)
- // 停留xxx秒
- time.Sleep(delay)
- t1 := time.Now()
- fmt.Println(name, " end at ", t1)
- // 計算時間差
- fmt.Println(name, " lasted ", t1.Sub(t0))
- }
- func main() {
- // 生成隨機種子, 類似C語言中的srand((unsigned)time(0))生成隨機種子
- rand.Seed(time.Now().Unix())
- // To convert an integer number of units to a Duration, multiply
- fmt.Println(time.Duration(5) * time.Second)
- var name string
- for i := 0; i < 3; i++ {
- name = fmt.Sprintf("go_%02d", i) // 生成ID
- // 生成隨機等待時間, 從0-4秒
- // ntn returns, as an int, a non-negative pseudo-random number in [0,n) from the default Source. It panics if n <= 0.
- go routine(name, time.Duration(rand.Intn(5))*time.Second)
- }
- // 讓主程式停住, 不然主程式退了, goroutine也就退了
- var input string
- fmt.Scanln(&input)
- fmt.Println("done")
- }
- /*
- output:
- mba:test gerryyang$ ./rand_t
- 5s
- go_00 start at 2013-12-28 13:25:04.460768468 +0800 HKT
- go_01 start at 2013-12-28 13:25:04.460844141 +0800 HKT
- go_02 start at 2013-12-28 13:25:04.460861337 +0800 HKT
- go_02 end at 2013-12-28 13:25:04.460984329 +0800 HKT
- go_02 lasted 122.992us
- go_01 end at 2013-12-28 13:25:05.462003787 +0800 HKT
- go_01 lasted 1.001159646s
- go_00 end at 2013-12-28 13:25:07.461884807 +0800 HKT
- go_00 lasted 3.001116339s
- done
- */
關於goroutine是否真正併發的問題,耗子叔叔這裡是這樣解釋的:
引用:
“關於goroutine,我試了一下,無論是Windows還是Linux,基本上來說是用作業系統的執行緒來實現的。不過,goroutine有個特性,也就是說,如果一個goroutine沒有被阻塞,那麼別的goroutine就不會得到執行。這並不是真正的併發,如果你要真正的併發,你需要在你的main函式的第一行加上下面的這段程式碼:
- import runtime
- runtime.GOMAXPROCS(n)
”
本人使用go1.2版本在Linux64,2.6.32核心環境下測試,在上述程式碼中再新增一個死迴圈的routine,可以驗證上述的邏輯。在沒有設定GOMAXPROCS引數時,多個goroutine會出現阻塞的情況;設定GOMAXPROCS引數時,下面的幾個routine可以正常執行不會被阻塞。
- package main
- import (
- "fmt"
- "math/rand"
- "time"
- "runtime"
- )
- func routine(name string, delay time.Duration) {
- t0 := time.Now()
- fmt.Println(name, " start at ", t0, ", sleep:", delay)
- // 停留xxx秒
- time.Sleep(delay)
- t1 := time.Now()
- fmt.Println(name, " end at ", t1)
- // 計算時間差
- fmt.Println(name, " lasted ", t1.Sub(t0))
- }
- func die_routine() {
- for {
- // die loop
- }
- }
- func main() {
- // 實現真正的併發
- runtime.GOMAXPROCS(4)
- fmt.Println("set runtime.GOMAXPROCS")
- // 生成隨機種子, 類似C語言中的srand((unsigned)time(0))生成隨機種子
- rand.Seed(time.Now().Unix())
- // To convert an integer number of units to a Duration, multiply
- fmt.Println(time.Duration(5) * time.Second)
- // die routine
- go die_routine()
- var name string
- for i := 0; i < 3; i++ {
- name = fmt.Sprintf("go_%02d", i) // 生成ID
- // 生成隨機等待時間, 從0-4秒
- // ntn returns, as an int, a non-negative pseudo-random number in [0,n) from the default Source. It panics if n <= 0.
- go routine(name, time.Duration(rand.Intn(5))*time.Second)
- }
- // 讓主程式停住, 不然主程式退了, goroutine也就退了
- var input string
- fmt.Scanln(&input)
- fmt.Println("done")
- }
1 goroutine非併發安全性問題
這是一個經常出現在教科書裡賣票的例子,啟了5個goroutine來賣票,賣票的函式sell_tickets很簡單,就是隨機的sleep一下,然後對全域性變數total_tickets作減一操作。
- package main
- import (
- "fmt"
- "time"
- "math/rand"
- "runtime"
- )
- var total_tickets int32 = 10
- func sell_tickets(i int) {
- for {
- // 如果有票就賣
- if total_tickets > 0 {
- time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
- // 賣一張票
- total_tickets--
- fmt.Println("id:", i, " ticket:", total_tickets)
- } else {
- break
- }
- }
- }
- func main() {
- // 設定真正意義上的併發
- runtime.GOMAXPROCS(4)
- // 生成隨機種子
- rand.Seed(time.Now().Unix())
- // 併發5個goroutine來賣票
- for i := 0; i < 5; i++ {
- go sell_tickets(i)
- }
- // 等待執行緒執行完
- var input string
- fmt.Scanln(&input)
- // 退出時列印還有多少票
- fmt.Println(total_tickets, "done")
- }
- /*
- output:
- id: 1 ticket: 8
- id: 0 ticket: 8
- id: 0 ticket: 7
- id: 2 ticket: 5
- id: 4 ticket: 6
- id: 4 ticket: 3
- id: 3 ticket: 3
- id: 1 ticket: 1
- id: 0 ticket: 2
- id: 3 ticket: -1
- id: 2 ticket: -1
- id: 1 ticket: -2
- id: 4 ticket: -3
- -3 done
- */
上述例子沒有考慮併發安全問題,因此需要加一把鎖以保證每個routine在售票的時候資料同步。
- package main
- import (
- "fmt"
- "time"
- "math/rand"
- "runtime"
- "sync"
- )
- var total_tickets int32 = 10
- var mutex = &sync.Mutex{}
- func sell_tickets(i int) {
- for total_tickets > 0 {
- mutex.Lock()
- // 如果有票就賣
- if total_tickets > 0 {
- time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
- // 賣一張票
- total_tickets--
- fmt.Println("id:", i, " ticket:", total_tickets)
- }
- mutex.Unlock()
- }
- }
- func main() {
- // 設定真正意義上的併發
- runtime.GOMAXPROCS(4)
- // 生成隨機種子
- rand.Seed(time.Now().Unix())
- // 併發5個goroutine來賣票
- for i := 0; i < 5; i++ {
- go sell_tickets(i)
- }
- // 等待執行緒執行完
- var input string
- fmt.Scanln(&input)
- // 退出時列印還有多少票
- fmt.Println(total_tickets, "done")
- }
- /*
- output:
- id: 0 ticket: 9
- id: 0 ticket: 8
- id: 0 ticket: 7
- id: 0 ticket: 6
- id: 0 ticket: 5
- id: 0 ticket: 4
- id: 0 ticket: 3
- id: 0 ticket: 2
- id: 0 ticket: 1
- id: 0 ticket: 0
- 0 done
- */
2 併發情況下的原子操作問題
go語言也支援原子操作。關於原子操作可以參考耗子叔叔這篇文章《無鎖佇列的實現》,裡面說到了一些CAS – CompareAndSwap的操作。下面的程式有10個goroutine,每個會對cnt變數累加20次,所以,最後的cnt應該是200。如果沒有atomic的原子操作,那麼cnt將有可能得到一個小於200的數。下面使用了atomic操作,所以是安全的。
- package main
- import (
- "fmt"
- "sync/atomic"
- "time"
- )
- func main() {
- var cnt uint32 = 0
- // 啟動10個goroutine
- for i := 0; i < 10; i++ {
- go func() {
- // 每個goroutine都做20次自增運算
- for i := 0; i < 20; i++ {
- time.Sleep(time.Millisecond)
- atomic.AddUint32(&cnt, 1)
- }
- }()
- }
- // 等待2s, 等goroutine完成
- time.Sleep(time.Second * 2)
- // 取最終結果
- cntFinal := atomic.LoadUint32(&cnt)
- fmt.Println("cnt:", cntFinal)
- }
- /*
- output:
- cnt: 200
- */
轉帖自http://blog.csdn.net/delphiwcdj/article/details/17630863
相關文章
- 再論 golang 環境配置建議Golang
- OpenStack入門之若干討論
- [技術討論]拜託,不要再本本主義了
- [技術討論]再談新概念的建立和應用
- 設計模式討論之abstract factory篇設計模式
- 第十一篇:Linux中許可權的再討論( 下 )Linux
- Effective c++(筆記) 之 雜項討論C++筆記
- 設計模式討論之abstract factory篇 (轉)設計模式
- 第十篇:Linux中許可權的再討論( 上 )Linux
- [Note] git清空.git目錄後,重新再git init & git remote的討論GitREM
- SetUnhandledExceptionFilter 的討論ExceptionFilter
- [iOS Monkey 討論帖] 整套新的 fastmonkey 討論iOSAST
- [技術討論]關於低耦合開發的討論
- 再來一篇不錯的文章,討論Interface與Abstract的關係
- JSP的中文問題再問(一些理論上的探討)JS
- 【話題討論】如何把運維人員從雞肋轉變成雞大腿的故事之討論運維
- 再論開源
- [譯] 討論 JS ⚡:文件JS
- httprunner 大佬討論群HTTP
- Ruby語言討論
- 我們現在沒有討論的但有必要討論的模式模式
- 再測Golang的JSON庫GolangJSON
- 再論“庫存控制”
- 再論NAT和穿透穿透
- 馴服爛程式碼之實踐、總結與討論
- 當我們在討論遊戲社群時,我們在討論什麼?遊戲
- js中分號的討論JS
- 《NewSQL與NoSQL的討論》SQL
- plsql--迴圈討論SQL
- 小組討論結果
- 討論:大家來討論一些連線涉及到的引數
- 【討論】論 cursor 在測試中的使用
- Go基礎學習六之併發concurrencyGo
- 再談方法論和模式模式
- [技術討論]多使用者(多公司)的資料庫設計討論資料庫
- 關於UI的一次討論——來自專案管理群的討論UI專案管理
- 評論專欄: 為執行建模,再論
- 討論專案合理分層