GoLang之Concurrency再讨论

kjfcpua · · 2868 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:kjfcpua

查看原文:GoLang之Concurrency再讨论

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2868 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传