美文网首页
并发实践中的思考

并发实践中的思考

作者: lizhuoming | 来源:发表于2018-11-24 10:28 被阅读0次

    为什么要写这篇博客

    最近在学习Go并发的时候,想写一个并发的求斐波那契数的程序。期间遇到了一些坑,所以来记录一下自己的想法。

    写并发程序中遇到的坑

    并发1.0

    使用最原始的方式,每次递归的时候,开一个协程去跑,将结果放入channel中。最终发现在求的数比较大的时候,并发比单线程还要慢。

    并发2.0

    于是我考虑应该是开的协程太多了,使用协程池来控制协程数量。代码如下:

    func mutiExample(pool *tunny.Pool, ch chan int64, n int64) {
        var ans int64
        switch n {
        case 0:
            ans = 0
        case 1, 2:
            ans = 1
        default:
            res := make(chan int64, 2)
    
            a := func() {
                fmt.Println("start ", n-1)
                mutiExample(pool, res, n-1)
            }
            b := func() {
                fmt.Println("end ", n-2)
                mutiExample(pool, res, n-2)
            }
            go pool.Process(a)
            go pool.Process(b)
            ans = <-res + <-res
        }
        ch <- ans
    }
    
    func main() {
        start := time.Now()
        pool2 := tunny.NewCallback(3)
        ch := make(chan int64, 1)
        mutiExample(pool2, ch, 5)
        fmt.Println(<-ch, time.Since(start))
    }
    

    发现的问题

    现象

    • 如果求的值为5,协程数量为3,一定概率会死锁;
    • 如果求的值为5,协程数量为4,则不会死锁;

    分析

    我们来通过一组结果分析它的执行过程

    start  4
    end  3
    end  1
    end  2
    start  3
    
    • 显然,程序先开了两个协程去跑 n=4 和 n=3 的情况,由于未获取全部返回,协程会一直阻塞;
    • 然后是 n=2 或 n=1 的情况,将结果写入channel,并结束;
    • 最后执行 n=3 的情况,由于3个协程全部被占用,且它们所期望的值无法返回,会造成死锁。


      流程.png

    结论

    如果要让程序不出现死锁,则需要限定协程池大小大于执行 n>2 的协程的总数量。
    因为这种情况下程序无法直接返回,在等待接收数据。

    自己的一些思考

    • 在当时遇到这个问题的时候,自己一直在想是不是自己程序出了问题,而没有对程序的执行流程进行分析。所以也是给自己提了个醒,在写程序的时候,要提前能想清楚程序的执行流程,不要盲目的依赖程序的运行结果。
    • 你会发现2.0程序的执行效率还是很低,可以发现这种递归程序,盲目的去开协程,是不会对效率有提升的。因为当递归层数很多时,协程池在调度协程等问题上会花费很多的时间。
    • 贴出代码的原因是因为自己的编码规范实在太差,比如:命名,取channel的值等。

    总结

    • 写并发程序时,首先要选取好合适的并发方案,不能为了并发而并发;
    • 并发程序不能太相信结果(协程执行顺序不确定),而是要想清楚代码执行的流程(道理)
    • 要加强自己对代码规范的要求
    • 要善用测试,这个以后再补充吧。。

    相关文章

      网友评论

          本文标题:并发实践中的思考

          本文链接:https://www.haomeiwen.com/subject/zldeqqtx.html