go 并发控制

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
        }
    }
} 

 注意,虽然并发得到了控制,但是协程的数量是一直在增加的,执行第三个代码就可以看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值