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
- */
有疑问加站长微信联系(非本文作者)