在循环中并发执行goroutine
看下面的例子,
package main
import (
"fmt"
"math/rand"
"time"
)
func sum(slice []int) (result int) {
ch := make(chan int)
fmt.Println(cap(slice))
fmt.Println(len(slice))
var num int = 20
var count int = len(slice) / num
for i := 0; i < num; i++ {
from := i * count
end := count * (i + 1)
subSlice := slice[from:end]
// 通过 goroutine 并发的计算
// 相当于启动了 20 个goroutine
go func(arr []int) {
var base int = 0
for _, v := range arr {
base += v
}
ch <- base
}(subSlice)
}
// Wait for goroutines to complete.
for i := 0; i < num; i++ {
x := <-ch
result += x
}
return
}
func sumOther(slice []int) (result int) {
// for range 遍历切片
for _, i := range slice {
result += i
}
return
}
func main() {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
slice := make([]int, 10000) //定义容量为10000的切片
for i := 0; i < 10000; i++ {
slice[i] = r.Intn(100)
}
s1 := time.Now().UnixNano()
res := sum(slice)
s2 := time.Now().UnixNano()
fmt.Printf("time=%d,res=%d\n", s2-s1, res)
s3 := time.Now().UnixNano()
res2 := sumOther(slice)
s4 := time.Now().UnixNano()
fmt.Printf("time=%d,res=%d\n", s4-s3, res2)
fmt.Println(".........")
}
运行结果:
10000
10000
time=108438,res=497836
time=6394,res=497836
上边的demo中使用随机数构造了一个容量和大小都是10000的切片,求切片中所有整数的和。这里把切片分成20份,使用一个单独的goroutine执行每份的求和,最后汇总结果。注意这里使用的channel不带缓冲,不带缓冲的channel工作过程如下: 一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作,当发送的值通过Channels成功传输之后,两个goroutine可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。基于无缓存Channels的发送和接收操作将导致两个goroutine做一次同步操作。因为这个原因,无缓存Channels有时候也被称为同步Channels。也就是说写入一次channel,必须要读一次channel。如果读取channnel在写入channel之前发生,那么发生阻塞,直到有数据写入channel。
所以这里使用无缓存的channel会带来一些同步操作,将这个demo改成使用带有缓存的channel看看效果如何,
package main
import (
"fmt"
"math/rand"
"time"
)
func sum(slice []int) (result int) {
ch := make(chan int)
var num int = 20
var count int = len(slice) / num
for i := 0; i < num; i++ {
from := i * count
end := count * (i + 1)
subSlice := slice[from:end]
// 通过 goroutine 并发的计算
// 相当于启动了 20 个goroutine
go func(arr []int) {
var base int = 0
for _, v := range arr {
base += v
}
// 一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作
ch <- base
}(subSlice)
}
// Wait for goroutines to complete.
for i := 0; i < num; i++ {
//channel是无缓冲的,接收方会一直阻塞直到有数据到来,发送方会一直阻塞直到接收方将数据取出。
x := <-ch
result += x
}
return
}
func sumWithCacheChannel(slice []int) (result int) {
var num int = 20
ch := make(chan int, num)
var count int = len(slice) / num
for i := 0; i < num; i++ {
from := i * count
end := count * (i + 1)
subSlice := slice[from:end]
// 通过 goroutine 并发的计算
// 相当于启动了 20 个goroutine
go func(arr []int) {
var base int = 0
for _, v := range arr {
base += v
}
//使用了带缓冲的channel ,发送数据不会阻塞
ch <- base
}(subSlice)
}
// 预先知道会有 20 个消息发送到channel中,所以这里读取20次,会保证读取到20个消息(没有消息时会阻塞)
for i := 0; i < num; i++ {
x := <-ch
result += x
}
return
}
func sumOther(slice []int) (result int) {
// for range 遍历切片
for _, i := range slice {
result += i
}
return
}
func main() {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
slice := make([]int, 10000) //定义容量为10000的切片
for i := 0; i < 10000; i++ {
slice[i] = r.Intn(100)
}
s1 := time.Now().UnixNano()
res := sum(slice)
s2 := time.Now().UnixNano()
fmt.Printf("time=%d,res=%d\n", s2-s1, res)
s3 := time.Now().UnixNano()
res2 := sumWithCacheChannel(slice)
s4 := time.Now().UnixNano()
fmt.Printf("time=%d,res=%d\n", s4-s3, res2)
s5 := time.Now().UnixNano()
res3 := sumOther(slice)
s6 := time.Now().UnixNano()
fmt.Printf("time=%d,res=%d\n", s6-s5, res3)
}
运行结果,
time=78906,res=491743
time=48826,res=491743
time=8240,res=491743
可以看到使用了带缓存的channel比没有使用带缓存的channel是有一点优势的,就是因为带缓存的channel发送消息不会阻塞,如果channel中有消息接收一端也不会阻塞。但是看来在这里使用goroutine也是多余的。
========END========