GoLang之Concurrency再討論

kjfcpua發表於2014-01-23

0 goroutine是否併發的問題

GoLang通過go關鍵字實現併發操作(真的併發嗎?),一個最簡單的併發模型:

  1. package main  
  2.   
  3. import (  
  4.     "fmt"  
  5.     "math/rand"  
  6.     "time"  
  7. )  
  8.   
  9. func routine(name string, delay time.Duration) {  
  10.     t0 := time.Now()  
  11.     fmt.Println(name, " start at ", t0)  
  12.   
  13.     // 停留xxx秒  
  14.     time.Sleep(delay)  
  15.   
  16.     t1 := time.Now()  
  17.     fmt.Println(name, " end at ", t1)  
  18.   
  19.     // 計算時間差  
  20.     fmt.Println(name, " lasted ", t1.Sub(t0))  
  21.   
  22. }  
  23.   
  24. func main() {  
  25.   
  26.     // 生成隨機種子, 類似C語言中的srand((unsigned)time(0))生成隨機種子  
  27.     rand.Seed(time.Now().Unix())  
  28.   
  29.     // To convert an integer number of units to a Duration, multiply  
  30.     fmt.Println(time.Duration(5) * time.Second)  
  31.   
  32.     var name string  
  33.     for i := 0; i < 3; i++ {  
  34.         name = fmt.Sprintf("go_%02d", i) // 生成ID  
  35.   
  36.         // 生成隨機等待時間, 從0-4秒  
  37.         // ntn returns, as an int, a non-negative pseudo-random number in [0,n) from the default Source. It panics if n <= 0.  
  38.         go routine(name, time.Duration(rand.Intn(5))*time.Second)  
  39.     }  
  40.   
  41.     // 讓主程式停住, 不然主程式退了, goroutine也就退了  
  42.     var input string  
  43.     fmt.Scanln(&input)  
  44.     fmt.Println("done")  
  45. }  
  46.   
  47. /* 
  48. output: 
  49.  
  50. mba:test gerryyang$ ./rand_t 
  51. 5s 
  52. go_00  start at  2013-12-28 13:25:04.460768468 +0800 HKT 
  53. go_01  start at  2013-12-28 13:25:04.460844141 +0800 HKT 
  54. go_02  start at  2013-12-28 13:25:04.460861337 +0800 HKT 
  55. go_02  end at  2013-12-28 13:25:04.460984329 +0800 HKT 
  56. go_02  lasted  122.992us 
  57. go_01  end at  2013-12-28 13:25:05.462003787 +0800 HKT 
  58. go_01  lasted  1.001159646s 
  59. go_00  end at  2013-12-28 13:25:07.461884807 +0800 HKT 
  60. go_00  lasted  3.001116339s 
  61.  
  62. done 
  63. */  

關於goroutine是否真正併發的問題,耗子叔叔這裡是這樣解釋的:

引用:

關於goroutine,我試了一下,無論是Windows還是Linux,基本上來說是用作業系統的執行緒來實現的。不過,goroutine有個特性,也就是說,如果一個goroutine沒有被阻塞,那麼別的goroutine就不會得到執行。這並不是真正的併發,如果你要真正的併發,你需要在你的main函式的第一行加上下面的這段程式碼:

  1. import runtime  
  2. runtime.GOMAXPROCS(n)  

本人使用go1.2版本在Linux64,2.6.32核心環境下測試,在上述程式碼中再新增一個死迴圈的routine,可以驗證上述的邏輯。在沒有設定GOMAXPROCS引數時,多個goroutine會出現阻塞的情況;設定GOMAXPROCS引數時,下面的幾個routine可以正常執行不會被阻塞。

  1. package main  
  2.   
  3. import (  
  4.     "fmt"  
  5.     "math/rand"  
  6.     "time"  
  7.     "runtime"  
  8. )  
  9.   
  10. func routine(name string, delay time.Duration) {  
  11.     t0 := time.Now()  
  12.     fmt.Println(name, " start at ", t0, ", sleep:", delay)  
  13.   
  14.     // 停留xxx秒    
  15.     time.Sleep(delay)  
  16.   
  17.     t1 := time.Now()  
  18.     fmt.Println(name, " end at ", t1)  
  19.   
  20.     // 計算時間差    
  21.     fmt.Println(name, " lasted ", t1.Sub(t0))  
  22.   
  23. }  
  24.   
  25. func die_routine() {  
  26.     for {  
  27.     // die loop  
  28.     }  
  29. }  
  30.   
  31. func main() {  
  32.   
  33.     // 實現真正的併發  
  34.     runtime.GOMAXPROCS(4)  
  35.   
  36.     fmt.Println("set runtime.GOMAXPROCS")  
  37.   
  38.     // 生成隨機種子, 類似C語言中的srand((unsigned)time(0))生成隨機種子    
  39.     rand.Seed(time.Now().Unix())  
  40.   
  41.     // To convert an integer number of units to a Duration, multiply    
  42.     fmt.Println(time.Duration(5) * time.Second)  
  43.   
  44.     // die routine  
  45.     go die_routine()  
  46.   
  47.     var name string  
  48.     for i := 0; i < 3; i++ {  
  49.         name = fmt.Sprintf("go_%02d", i) // 生成ID    
  50.   
  51.         // 生成隨機等待時間, 從0-4秒    
  52.         // ntn returns, as an int, a non-negative pseudo-random number in [0,n) from the default Source. It panics if n <= 0.    
  53.         go routine(name, time.Duration(rand.Intn(5))*time.Second)  
  54.     }  
  55.   
  56.     // 讓主程式停住, 不然主程式退了, goroutine也就退了    
  57.     var input string  
  58.     fmt.Scanln(&input)  
  59.     fmt.Println("done")  
  60. }  

1 goroutine非併發安全性問題

這是一個經常出現在教科書裡賣票的例子,啟了5個goroutine來賣票,賣票的函式sell_tickets很簡單,就是隨機的sleep一下,然後對全域性變數total_tickets作減一操作。

  1. package main  
  2.   
  3. import (  
  4.     "fmt"  
  5.     "time"  
  6.     "math/rand"  
  7.     "runtime"  
  8. )  
  9.   
  10. var total_tickets int32 = 10  
  11.   
  12. func sell_tickets(i int) {  
  13.     for {  
  14.         // 如果有票就賣  
  15.         if total_tickets > 0 {  
  16.             time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)  
  17.             // 賣一張票  
  18.             total_tickets--  
  19.             fmt.Println("id:", i, " ticket:", total_tickets)  
  20.         } else {  
  21.             break  
  22.         }  
  23.     }  
  24. }  
  25.   
  26. func main() {  
  27.   
  28.     // 設定真正意義上的併發  
  29.     runtime.GOMAXPROCS(4)  
  30.   
  31.     // 生成隨機種子  
  32.     rand.Seed(time.Now().Unix())  
  33.   
  34.     // 併發5個goroutine來賣票  
  35.     for i := 0; i < 5; i++ {  
  36.         go sell_tickets(i)  
  37.     }  
  38.   
  39.     // 等待執行緒執行完  
  40.     var input string  
  41.     fmt.Scanln(&input)  
  42.     // 退出時列印還有多少票  
  43.     fmt.Println(total_tickets, "done")  
  44. }  
  45. /* 
  46. output: 
  47.  
  48. id: 1  ticket: 8 
  49. id: 0  ticket: 8 
  50. id: 0  ticket: 7 
  51. id: 2  ticket: 5 
  52. id: 4  ticket: 6 
  53. id: 4  ticket: 3 
  54. id: 3  ticket: 3 
  55. id: 1  ticket: 1 
  56. id: 0  ticket: 2 
  57. id: 3  ticket: -1 
  58. id: 2  ticket: -1 
  59. id: 1  ticket: -2 
  60. id: 4  ticket: -3 
  61.  
  62. -3 done 
  63. */  

上述例子沒有考慮併發安全問題,因此需要加一把鎖以保證每個routine在售票的時候資料同步。

  1. package main  
  2.   
  3. import (  
  4.     "fmt"  
  5.     "time"  
  6.     "math/rand"  
  7.     "runtime"  
  8.     "sync"  
  9. )  
  10.   
  11. var total_tickets int32 = 10  
  12. var mutex = &sync.Mutex{}  
  13.   
  14. func sell_tickets(i int) {  
  15.   
  16.     for total_tickets > 0 {  
  17.   
  18.         mutex.Lock()  
  19.         // 如果有票就賣  
  20.         if total_tickets > 0 {  
  21.             time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)  
  22.             // 賣一張票  
  23.             total_tickets--  
  24.             fmt.Println("id:", i, " ticket:", total_tickets)  
  25.         }  
  26.         mutex.Unlock()  
  27.     }  
  28. }  
  29.   
  30. func main() {  
  31.   
  32.     // 設定真正意義上的併發  
  33.     runtime.GOMAXPROCS(4)  
  34.   
  35.     // 生成隨機種子  
  36.     rand.Seed(time.Now().Unix())  
  37.   
  38.     // 併發5個goroutine來賣票  
  39.     for i := 0; i < 5; i++ {  
  40.         go sell_tickets(i)  
  41.     }  
  42.   
  43.     // 等待執行緒執行完  
  44.     var input string  
  45.     fmt.Scanln(&input)  
  46.     // 退出時列印還有多少票  
  47.     fmt.Println(total_tickets, "done")  
  48. }  
  49. /* 
  50. output: 
  51.  
  52. id: 0  ticket: 9 
  53. id: 0  ticket: 8 
  54. id: 0  ticket: 7 
  55. id: 0  ticket: 6 
  56. id: 0  ticket: 5 
  57. id: 0  ticket: 4 
  58. id: 0  ticket: 3 
  59. id: 0  ticket: 2 
  60. id: 0  ticket: 1 
  61. id: 0  ticket: 0 
  62.  
  63. 0 done 
  64. */  

2 併發情況下的原子操作問題

go語言也支援原子操作。關於原子操作可以參考耗子叔叔這篇文章《無鎖佇列的實現》,裡面說到了一些CAS – CompareAndSwap的操作。下面的程式有10個goroutine,每個會對cnt變數累加20次,所以,最後的cnt應該是200。如果沒有atomic的原子操作,那麼cnt將有可能得到一個小於200的數。下面使用了atomic操作,所以是安全的。

  1. package main  
  2.   
  3. import (  
  4.     "fmt"  
  5.     "sync/atomic"  
  6.     "time"  
  7. )  
  8.   
  9. func main() {  
  10.   
  11.     var cnt uint32 = 0  
  12.   
  13.     // 啟動10個goroutine  
  14.     for i := 0; i < 10; i++ {  
  15.         go func() {  
  16.             // 每個goroutine都做20次自增運算  
  17.             for i := 0; i < 20; i++ {  
  18.                 time.Sleep(time.Millisecond)  
  19.                 atomic.AddUint32(&cnt, 1)  
  20.             }  
  21.         }()  
  22.     }  
  23.   
  24.     // 等待2s, 等goroutine完成  
  25.     time.Sleep(time.Second * 2)  
  26.     // 取最終結果  
  27.     cntFinal := atomic.LoadUint32(&cnt)  
  28.   
  29.     fmt.Println("cnt:", cntFinal)  
  30. }  
  31. /* 
  32. output: 
  33.  
  34. cnt: 200 
  35. */  




轉帖自http://blog.csdn.net/delphiwcdj/article/details/17630863

相關文章