分享
  1. 首页
  2. 文章

GoLang之Concurrency再讨论

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

0 goroutine是否并发的问题

GoLang通过go关键字实现并发操作(真的并发吗?),一个最简单的并发模型:

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

关于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. import (
  3. "fmt"
  4. "math/rand"
  5. "time"
  6. "runtime"
  7. )
  8. func routine(name string, delay time.Duration) {
  9. t0 := time.Now()
  10. fmt.Println(name, " start at ", t0, ", sleep:", delay)
  11. // 停留xxx秒
  12. time.Sleep(delay)
  13. t1 := time.Now()
  14. fmt.Println(name, " end at ", t1)
  15. // 计算时间差
  16. fmt.Println(name, " lasted ", t1.Sub(t0))
  17. }
  18. func die_routine() {
  19. for {
  20. // die loop
  21. }
  22. }
  23. func main() {
  24. // 实现真正的并发
  25. runtime.GOMAXPROCS(4)
  26. fmt.Println("set runtime.GOMAXPROCS")
  27. // 生成随机种子, 类似C语言中的srand((unsigned)time(0))生成随机种子
  28. rand.Seed(time.Now().Unix())
  29. // To convert an integer number of units to a Duration, multiply
  30. fmt.Println(time.Duration(5) * time.Second)
  31. // die routine
  32. go die_routine()
  33. var name string
  34. for i := 0; i < 3; i++ {
  35. name = fmt.Sprintf("go_%02d", i) // 生成ID
  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. // 让主进程停住, 不然主进程退了, goroutine也就退了
  41. var input string
  42. fmt.Scanln(&input)
  43. fmt.Println("done")
  44. }

1 goroutine非并发安全性问题

这是一个经常出现在教科书里卖票的例子,启了5个goroutine来卖票,卖票的函数sell_tickets很简单,就是随机的sleep一下,然后对全局变量total_tickets作减一操作。
  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "math/rand"
  6. "runtime"
  7. )
  8. var total_tickets int32 = 10
  9. func sell_tickets(i int) {
  10. for {
  11. // 如果有票就卖
  12. if total_tickets > 0 {
  13. time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
  14. // 卖一张票
  15. total_tickets--
  16. fmt.Println("id:", i, " ticket:", total_tickets)
  17. } else {
  18. break
  19. }
  20. }
  21. }
  22. func main() {
  23. // 设置真正意义上的并发
  24. runtime.GOMAXPROCS(4)
  25. // 生成随机种子
  26. rand.Seed(time.Now().Unix())
  27. // 并发5个goroutine来卖票
  28. for i := 0; i < 5; i++ {
  29. go sell_tickets(i)
  30. }
  31. // 等待线程执行完
  32. var input string
  33. fmt.Scanln(&input)
  34. // 退出时打印还有多少票
  35. fmt.Println(total_tickets, "done")
  36. }
  37. /*
  38. output:
  39. id: 1 ticket: 8
  40. id: 0 ticket: 8
  41. id: 0 ticket: 7
  42. id: 2 ticket: 5
  43. id: 4 ticket: 6
  44. id: 4 ticket: 3
  45. id: 3 ticket: 3
  46. id: 1 ticket: 1
  47. id: 0 ticket: 2
  48. id: 3 ticket: -1
  49. id: 2 ticket: -1
  50. id: 1 ticket: -2
  51. id: 4 ticket: -3
  52. -3 done
  53. */

上述例子没有考虑并发安全问题,因此需要加一把锁以保证每个routine在售票的时候数据同步。

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "math/rand"
  6. "runtime"
  7. "sync"
  8. )
  9. var total_tickets int32 = 10
  10. var mutex = &sync.Mutex{}
  11. func sell_tickets(i int) {
  12. for total_tickets > 0 {
  13. mutex.Lock()
  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. }
  21. mutex.Unlock()
  22. }
  23. }
  24. func main() {
  25. // 设置真正意义上的并发
  26. runtime.GOMAXPROCS(4)
  27. // 生成随机种子
  28. rand.Seed(time.Now().Unix())
  29. // 并发5个goroutine来卖票
  30. for i := 0; i < 5; i++ {
  31. go sell_tickets(i)
  32. }
  33. // 等待线程执行完
  34. var input string
  35. fmt.Scanln(&input)
  36. // 退出时打印还有多少票
  37. fmt.Println(total_tickets, "done")
  38. }
  39. /*
  40. output:
  41. id: 0 ticket: 9
  42. id: 0 ticket: 8
  43. id: 0 ticket: 7
  44. id: 0 ticket: 6
  45. id: 0 ticket: 5
  46. id: 0 ticket: 4
  47. id: 0 ticket: 3
  48. id: 0 ticket: 2
  49. id: 0 ticket: 1
  50. id: 0 ticket: 0
  51. 0 done
  52. */

2 并发情况下的原子操作问题

go语言也支持原子操作。关于原子操作可以参考耗子叔叔这篇文章《无锁队列的实现 》,里面说到了一些CAS – CompareAndSwap的操作。下面的程序有10个goroutine,每个会对cnt变量累加20次,所以,最后的cnt应该是200。如果没有atomic的原子操作,那么cnt将有可能得到一个小于200的数。下面使用了atomic操作,所以是安全的。
  1. package main
  2. import (
  3. "fmt"
  4. "sync/atomic"
  5. "time"
  6. )
  7. func main() {
  8. var cnt uint32 = 0
  9. // 启动10个goroutine
  10. for i := 0; i < 10; i++ {
  11. go func() {
  12. // 每个goroutine都做20次自增运算
  13. for i := 0; i < 20; i++ {
  14. time.Sleep(time.Millisecond)
  15. atomic.AddUint32(&cnt, 1)
  16. }
  17. }()
  18. }
  19. // 等待2s, 等goroutine完成
  20. time.Sleep(time.Second * 2)
  21. // 取最终结果
  22. cntFinal := atomic.LoadUint32(&cnt)
  23. fmt.Println("cnt:", cntFinal)
  24. }
  25. /*
  26. output:
  27. cnt: 200
  28. */




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

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

本文来自:CSDN博客

感谢作者:kjfcpua

查看原文:GoLang之Concurrency再讨论

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

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

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏