GoLang之Concurrency再讨论

来源:互联网 发布:linux安装oracle9i 编辑:程序博客网 时间:2024/06/06 20:12

2013-12-28 wcdj


0 goroutine是否并发的问题

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

package mainimport ("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, multiplyfmt.Println(time.Duration(5) * time.Second)var name stringfor 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 stringfmt.Scanln(&input)fmt.Println("done")}/*output:mba:test gerryyang$ ./rand_t5sgo_00  start at  2013-12-28 13:25:04.460768468 +0800 HKTgo_01  start at  2013-12-28 13:25:04.460844141 +0800 HKTgo_02  start at  2013-12-28 13:25:04.460861337 +0800 HKTgo_02  end at  2013-12-28 13:25:04.460984329 +0800 HKTgo_02  lasted  122.992usgo_01  end at  2013-12-28 13:25:05.462003787 +0800 HKTgo_01  lasted  1.001159646sgo_00  end at  2013-12-28 13:25:07.461884807 +0800 HKTgo_00  lasted  3.001116339sdone*/

关于goroutine是否真正并发的问题,耗子叔叔这里是这样解释的:

引用:

关于goroutine,我试了一下,无论是Windows还是Linux,基本上来说是用操作系统的线程来实现的。不过,goroutine有个特性,也就是说,如果一个goroutine没有被阻塞,那么别的goroutine就不会得到执行。这并不是真正的并发,如果你要真正的并发,你需要在你的main函数的第一行加上下面的这段代码:

import runtimeruntime.GOMAXPROCS(n)

本人使用go1.2版本在Linux64,2.6.32内核环境下测试,在上述代码中再添加一个死循环的routine,可以验证上述的逻辑。在没有设置GOMAXPROCS参数时,多个goroutine会出现阻塞的情况;设置GOMAXPROCS参数时,下面的几个routine可以正常执行不会被阻塞。

package mainimport (    "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 mainimport ("fmt""time""math/rand""runtime")var total_tickets int32 = 10func 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 stringfmt.Scanln(&input)// 退出时打印还有多少票fmt.Println(total_tickets, "done")}/*output:id: 1  ticket: 8id: 0  ticket: 8id: 0  ticket: 7id: 2  ticket: 5id: 4  ticket: 6id: 4  ticket: 3id: 3  ticket: 3id: 1  ticket: 1id: 0  ticket: 2id: 3  ticket: -1id: 2  ticket: -1id: 1  ticket: -2id: 4  ticket: -3-3 done*/

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

package mainimport ("fmt""time""math/rand""runtime""sync")var total_tickets int32 = 10var 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 stringfmt.Scanln(&input)// 退出时打印还有多少票fmt.Println(total_tickets, "done")}/*output:id: 0  ticket: 9id: 0  ticket: 8id: 0  ticket: 7id: 0  ticket: 6id: 0  ticket: 5id: 0  ticket: 4id: 0  ticket: 3id: 0  ticket: 2id: 0  ticket: 1id: 0  ticket: 00 done*/

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

go语言也支持原子操作。关于原子操作可以参考耗子叔叔这篇文章《无锁队列的实现》,里面说到了一些CAS – CompareAndSwap的操作。下面的程序有10个goroutine,每个会对cnt变量累加20次,所以,最后的cnt应该是200。如果没有atomic的原子操作,那么cnt将有可能得到一个小于200的数。下面使用了atomic操作,所以是安全的。

package mainimport ("fmt""sync/atomic""time")func main() {var cnt uint32 = 0// 启动10个goroutinefor 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*/





0 0
原创粉丝点击