• (四十二)golang--协程之间通信的方式


    假设我们现在有这么一个需求:

    计算1-200之间各个数的阶乘,并将每个结果保存在mao中,最终显示出来,要求使用goroutime。

    分析:

    (1)使用goroutime完成,效率高,但是会出现并发/并行安全问题;

    (2)不同协程之间如何通信;

    • 对于(1):不同协程之间可能同时对一块内存进行操作,导致数据的混乱,即并发/并行不安全;主协程运行完了,计算阶乘的协程却没有运行完,功能并不能够准确实现;可利用互斥锁解决该问题;
    • 对于(2):可以利用利用管道;

    正常的代码:

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    var (
        myMap = make(map[int]int, 10)
    )
    
    func cal(n int) {
        res := 1
        for i := 1; i <= n; i++ {
            res *= i
        }
        myMap[n] = res
    }
    
    func main() {
        for i := 1; i <= 15; i++ {
            go cal(i)
        }
        for i, v := range myMap {
            fmt.Printf("map[%d]=%d
    ", i, v)
        }
    }

    运行结果:

    1.利用互斥锁 

    package main
    
    import (
        "fmt"
        "sync"
      ""  
    ) var ( myMap = make(map[int]int, 10) //lock是全局互斥锁,synchornized lock sync.Mutex ) func cal(n int) { res := 1 for i := 1; i <= n; i++ { res *= i } lock.Lock() myMap[n] = res lock.Unlock() } func main() { for i := 1; i <= 15; i++ { go cal(i) } for i, v := range myMap { fmt.Printf("map[%d]=%d ", i, v) } }

    有可能主程序运行完了而cal还没运行完(上面结果只到13,没有14,15),需要加上time.Sleep(time.Seconde*3),而在输出时,由于主协程并不知道程序已经完成了,底层仍然可能出现竞争资源,所以在输出阶段也要加上互斥锁。最终代码如下:

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    var (
        myMap = make(map[int]int, 10)
        //lock是全局互斥锁,synchornized
        lock sync.Mutex
    )
    
    func cal(n int) {
        res := 1
        for i := 1; i <= n; i++ {
            res *= i
        }
        lock.Lock()
        myMap[n] = res
        lock.Unlock()
    }
    
    func main() {
        for i := 1; i <= 15; i++ {
            go cal(i)
        }
    
        time.Sleep(time.Second * 4)
    
        lock.Lock()
        for i, v := range myMap {
            fmt.Printf("map[%d]=%d
    ", i, v)
        }
        lock.Unlock()
    }

    为什么需要管道?

    (1)主线程在等待所有协程全部完成的时间很难确定;

    (2)如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能协程还处于工作状态,这时也会随着主协程的结束而销毁;

    (3)通过全局变量加锁同步来实现通讯,也并不利于多个协程对全局变量的读写操作;

    管道的介绍:
    (1)管道的本质就是一种数据结构--队列;

    (2)数据先进先出;

    (3)线程安全,多协程访问时,不需要加锁;

    (4)管道只能存储相同的数据类型;

    管道的声明:

    var intChan chan int;

    var stringChan chan string;

    var mapChan chan map[int]string;

    var perChan chan Person;

    var perChan chan *Person;

    注意:管道是引用类型;管道必须初始化后才能写入数据;管道是有类型的,即IntChan只能写入int;

    管道初始化:

    var intChan chan int

    intChan = make(chan int,10) 

    向管道中读写数据:

    num := 10

    intChan<-num

    var num2 int

    num2<-intChan

    注意:管道容量满了则不能继续写入,在没有使用协程的情况下,管道空了不能继续读取。

    如何使管道中存储任意数据类型?

  • 相关阅读:
    窗体吸附 Timer + 判断Location (简单实用)
    C# FTP 应用程序
    C# 加密方法汇总
    LINQ 标准的查询操作符 合计操作符 Count()、Sum()、Min()、Max()、Average()和Aggregate()
    委托中的协变和逆变(C# 编程指南)
    深入探讨C#序列化和反序列化
    grep 命令详解
    Oracle 数据库的启动和关闭的方式!
    linux 下的光盘拷贝
    C3P0连接池配置
  • 原文地址:https://www.cnblogs.com/xiximayou/p/11952988.html
Copyright © 2020-2023  润新知