2013-12-28 wcdj
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
*/