go语言天生支持高并发,同时访问几千几万网页不是问题。例如在写网络爬虫时,我们从根页面找出其他的页面,然后其他的页面又找出其他的页面,如此反复。虽然go可以支持同时访问那么多页面,但是操作系统却不支持同时打开那么多页面,因为每次访问页面都是一次socket通信。每次socket通信就会占用文件描述符fd,操作系统同时支持打开的fd是有限制的。所以有必要做并发控制。
下面模拟爬虫的实验。有一个函数每次随机产生0-30个int型数组。对数组遍历时又根据那个函数产生随机数组。我们先看看不做并发控制的代码:
package main
import (
"fmt"
"time"
"math/rand"
"sync/atomic"
)
var Sum int32 = 0 //当前打开fd的总量
func produceInt() []int {
defer func() {
atomic.AddInt32(&Sum, -1)
}()
atomic.AddInt32(&Sum, 1)
rnd := rand.Intn(30) //这次产生的int的数量
var list []int
for i := 0; i < rnd; i++ {
list = append(list, rand.Intn(100))
}
time.Sleep(time.Millisecond * 100)
return list
}
func main() {
workList := make(chan []int)
go func() {
workList <- []int{1}
}()
go func() {
for {
fmt.Println("\nsum is ", Sum) //打印当前fd的数量
time.Sleep(time.Second)
}
}()
for list := range workList { //list仍然是个[]int
for range list {
go func() {
workList <- produceInt()
}()
}
}
}
运行当前的代码,发现fd是一直不停的在增加的。我们开始想办法控制并发。
最简单的方法就是利用chan来实现:
package main
import (
"fmt"
"time"
"math/rand"
"sync/atomic"
)
var Sum int32 = 0 //当前打开fd的总量
var tokens = make(chan struct{}, 30)
func produceInt() []int {
defer func() {
atomic.AddInt32(&Sum, -1)
}()
tokens <- struct{}{} //用chan同步来控制
atomic.AddInt32(&Sum, 1)
rnd := rand.Intn(30) //这次产生的int的数量
var list []int
for i := 0; i < rnd; i++ {
list = append(list, rand.Intn(100))
}
time.Sleep(time.Millisecond * 100)
<- tokens
return list
}
func main() {
workList := make(chan []int)
go func() {
workList <- []int{1}
}()
go func() {
for {
fmt.Println("\nsum is ", Sum) //打印当前fd的数量
time.Sleep(time.Second)
}
}()
for list := range workList { //list仍然是个[]int
for range list {
go func() {
workList <- produceInt()
}()
}
}
}
另外一种写法就是,利用chan,每次只取数组元素中的一个。然后开启N个这样的协程,代码如下:
package main
import (
"fmt"
"time"
"math/rand"
"sync/atomic"
"runtime"
)
var Sum int32 = 0 //当前打开fd的总量
func produceInt() []int {
defer func() {
atomic.AddInt32(&Sum, -1)
}()
atomic.AddInt32(&Sum, 1)
rnd := rand.Intn(30) //这次产生的int的数量
var list []int
for i := 0; i < rnd; i++ {
list = append(list, rand.Intn(100))
}
time.Sleep(time.Millisecond * 100)
return list
}
func main() {
workList := make(chan []int)
unseen := make(chan int)
go func() {
for {
fmt.Printf("sum is %d", Sum) //打印当前fd的数量
fmt.Printf("num of co is %d\n", runtime.NumGoroutine()) //当前协程的数量
time.Sleep(time.Second)
}
}()
go func() {
workList <- []int{1}
}()
for i := 0; i < 20; i++ {
go func() {
for range unseen {
k := produceInt()
go func() {
workList <- k
}()
}
}()
}
for list := range workList {
for _, l := range list {
unseen <- l
}
}
}
注意,虽然并发得到了控制,但是协程的数量是一直在增加的,执行第三个代码就可以看。