[go学习笔记.第十四章.协程和管道] 3.协程配合管道案例以及管道的注意事项和使用细节

news2025/7/18 7:56:43

案例一

请完成goroutine和channel协同工作的案例,具体要求:

(1).开启一个writeData协程,向管道intChan中写入50个整数.

(2).开启一个readData协程,从管道intChan中读取writeData写入的数据

(3).注意: writeData和readDate操作的是同一个管道

(4).主线程需要等待writeData和readDate协程都完成工作才能退出

 示例图如下:

代码如下: 

package main

import (
    "fmt"
)

//write data
func WriteData(intChan chan int) {
    for i := 0; i < 50; i++ {
        intChan <- i //放入数据
        fmt.Printf("write 数据=%v\n", i)
    }
    //关闭管道
    close(intChan)
}
//read data
func ReadData(intChan chan int, exitChan chan bool)  {
    for {
        v, ok := <-intChan
        if !ok {
            break
        }
        fmt.Printf("read 读取到的数据=%v\n", v)
    }
    //读取完成后,任务完成
    exitChan <- true
    close(exitChan)
}
func main()  {
    //创建两个管道
    intChan := make(chan int, 50)
    exitChan := make(chan bool, 1)
    go WriteData(intChan)
    go ReadData(intChan, exitChan)
    //判断
    for {
        _, ok := <- exitChan
        if !ok {
            break
        }
    }
}

阻塞

func main(){
intChan := make(chan int, 10) // 10->50的话数据一下就放入了 exitChan:=make(chan bool,1)

go writeData(intChan)

go readData(intChan,exitChan)

//就是为了等待..readData协程完成
for _ = range exitChan{
    fmt.Println("ok...") }
}

问题:

        如果注销掉 go readData(intChan,exitChan),程序会怎么样?

答:

        如果只是向管道写入数据,而没有读取(如果编译器(运行),发现一个管道只有写,而没有读,则该管道会阻塞;写管道和读管道的频率不一致,不影响),就会出现阻塞而dead lock,原因是intChan容量是10,而代码writeData会写入50个数据,因此会阻塞在writeData的 ch<-i

案例二

要求:

(1).启动一个协程,将1~2000的数放入到一个channel中,比如numChan

(2).启动8个协程,从numChan取出数(比如n),并计算1+...+n的值,并存放到resChan

(3).最后8个协呈协同完成工作后,再遍历resChan,显示结果[如res[1]=1..res[10]=55..]

注意:考虑resChan chan int是否合适?

案例三

goroutine+ channel配合完成排序,并写入文件

要求:

(1).开一个协程writeDataToFile,随机生成1000个数据,存放到文件中

(2).当writeDataToFile完成写1000个数据到文件后,让sort协程从文件中读取1000个文件,并完成排序,重新写入到另外一个文件

(3).考察点:协程和管道+文件的综合使用

(4).功能扩展:开10个协程writeDataToFile,每个协程随机生成1000个数据,存放到10文件中

(5).当10个文件都生成了,让10个sort协程从10文件中读取1000个文件,并完成排序,重新写入到10个结果文件

案例四

(1).创建一个 Person 结构体 [Name,Age,Address]

(2).使用rand方法配合随机创建10个Person实例,并放入到channel中

(3).遍历channel,将各个Person实例的信息显示在终端

package main

import (
    "fmt"
    "math/rand"
    "strconv"
)

type Person struct {
    Name string
    Age int
    Address string 
}
//创建Person实例,并写入chan
func WriteChan(personChan chan Person)  {
    for i := 0; i < 100; i++ {
       //把person放入管道中
       personChan <- Person {
        Name : "Person" +  strconv.Itoa(rand.Intn(100)),
        Age : rand.Intn(100), 
        Address : "成都市华府大道"  +  strconv.Itoa(rand.Intn(100)) + "号",
       }
       fmt.Println("wtite data=", i)
    }
    //关闭管道
    close(personChan)
}
//读取chan数据,并显示到终端
func ReadChan(personChan chan Person, exitChan chan bool)  {
    i := 0
    for {
        i++
        v , ok := <- personChan
        if !ok {
            break
        }
        fmt.Printf("第%v个person数据的Name=%v,Age=%v, Address=%v\n", i,
        v.Name, v.Age, v.Address)   
    }
    读取完成后,任务完成
    exitChan <- true
    close(exitChan)
}
func main()  {
    //创建两个管道
    personChan := make(chan Person, 100)
    exitChan := make(chan bool, 1)
    go WriteChan(personChan)
    go ReadChan(personChan, exitChan)
    for {
        _, ok := <- exitChan
        if !ok {
            break
        }
    }
}

 案例五

需求:

        要求统计 1 ~ 200000 的数字中,哪些是素数?

分析思路:

        传统的方法,就是使用一个循环,循环的判断各个数是不是素数

使用并发并行的方式,将统计素数的任务分配给多个( 4 个) gorontine去完成,完成任务时间短

示意图如下: 

 代码如下: 

package main

import (
    "fmt"
    "time"
)

//向intChan放入1~8000
func putNum(intChan chan int)  {
    for i := 1; i <= 1000; i++ {
        intChan<- i 
    }
    //关闭intChan
    close(intChan)
}
//从intChan取出并判断是否素数,如果是,就放入primeChan中,当前协程完成后,需设置exitChan退出标识
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool)  {
    var flag bool // 是不是素数标识符
    for {
        time.Sleep(time.Millisecond * 10)
        //从intChan取出数据,并判断是不是素数
        num, ok := <-intChan
        if !ok {
            break
        }
        flag = true
        for i := 2; i < num; i++ {
            if  num % i == 0 { // 说明不是素数
                flag = false
                break
            }
        }
        if flag {
            //将结果放入primeChan
            primeChan<- num 
        }
    }
    fmt.Println("有一个primeChan取不到数据,退出")
    //在这里还不能关闭primeChan,因为不知道哪一个primeChan还在执行
    //向exitChan放入true
    exitChan<- true
}
func main() {
    //创建3个管道
    intChan := make(chan int, 1000) //存放1~8000的数
    primeChan := make(chan int, 1000) //放入结果
    exitChan := make(chan bool, 4) //标识退出的管道
    //开启一个协程,向intChan放入1~8000个数
    go putNum(intChan)
    //开启4个协程,从intChan取出并判断是否素数,如果是,就放入primeChan中
    for i := 1; i <= 4; i++ {
        go primeNum(intChan, primeChan, exitChan)
    }

    //判断primeChan是否执行完毕,并关闭prizeChan
    go func(){
        for i := 1; i <= 4; i++ {
            <-exitChan
        }   
        //当从exitChan中取出4个数后,就可以关闭primeChan了
        close(primeChan)
    }()
    //遍历primeChan
    for {
        res, ok := <-primeChan
        if !ok {
            break
        }
        //输出
        fmt.Printf("素数:%d\n", res)
    }
    fmt.Println("main主线程退出")
}

看看执行的速度:

package main

import (
    "fmt"
    "time"
)

//向intChan放入1~8000
func putNum(intChan chan int)  {
    for i := 1; i <= 80000; i++ {
        intChan<- i 
    }
    //关闭intChan
    close(intChan)
}
//从intChan取出并判断是否素数,如果是,就放入primeChan中,当前协程完成后,需设置exitChan退出标识
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool)  {
    var flag bool // 是不是素数标识符
    for {
        // time.Sleep(time.Millisecond * 10)
        //从intChan取出数据,并判断是不是素数
        num, ok := <-intChan
        if !ok {
            break
        }
        flag = true
        for i := 2; i < num; i++ {
            if  num % i == 0 { // 说明不是素数
                flag = false
                break
            }
        }
        if flag {
            //将结果放入primeChan
            primeChan<- num 
        }
    }
    fmt.Println("有一个primeChan取不到数据,退出")
    //在这里还不能关闭primeChan,因为不知道哪一个primeChan还在执行
    //向exitChan放入true
    exitChan<- true
}
func main() {
    //创建3个管道
    intChan := make(chan int, 1000) //存放1~8000的数
    primeChan := make(chan int, 1000) //放入结果
    exitChan := make(chan bool, 4) //标识退出的管道

    //开始时间
    start := time.Now().Unix()  
    //开启一个协程,向intChan放入1~8000个数
    go putNum(intChan)
    //开启4个协程,从intChan取出并判断是否素数,如果是,就放入primeChan中
    for i := 1; i <= 4; i++ {
        go primeNum(intChan, primeChan, exitChan)
    }

    //判断primeChan是否执行完毕,并关闭prizeChan
    go func(){
        for i := 1; i <= 4; i++ {
            <-exitChan
        }   
        //结束时间
        end := time.Now().Unix()    
        fmt.Println("使用协程耗费的时间:", end - start)
        //当从exitChan中取出4个数后,就可以关闭primeChan了
        close(primeChan)
    }()
    //遍历primeChan
    for {
        _, ok := <-primeChan
        if !ok {
            break
        }
        //输出
        //fmt.Printf("素数:%d\n", res)
    }
    fmt.Println("main主线程退出")
}

 结论:使用go协程后,执行速度比普通方法提高至少4倍

注意事项

1.管道可以声明为只读或只写

package main

import (
    "fmt"
)

func main() {
    //管道可以声明为只读或只写
    //1.在默认的情况下,管道是双向的
    // var chan1 chan int//可读可写
    //2.声明为只写
    var chan2 chan<- int
    chan2 = make(chan int, 3)
    chan2<- 33
    // num := <-chan2 //error
    fmt.Println("chan2=", chan2)
    //2.声明为只读
    var chan3 <-chan int
    chan3 = make(chan int, 3)
    num := <-chan3 
    //chan3<- 33//error
    
    fmt.Println("num=", num)
}

2.使用select可以解决从管道取数据的阻塞问题

package main

import (
    "fmt"
)

func main()  {
    //使用select可以解决从管道取数据的阻塞问题
    //1.定义一个管道 10个数据int
    intChan := make(chan int, 10)
    for i := 0; i < 10; i++ {
        intChan <- i 
    }
    //2.定义一个管道 5个数据string   
    strChan := make(chan string, 5)
    for i := 0; i < 5; i++ {
        strChan <- "hello" + fmt.Sprintf("%d", i) 
    }

    //传统的方式在遍历管道时,如果不关闭会阻塞而导致deallock
    
    //问题:在实际开发中,有可能不好确定在什么情况下去关闭管道
    //可以使用select方式解决
    for {
        select {
            //注意,如果intChan一直没有关闭,不会一直阻塞而deallock,会自动到下一个case匹配
        case v := <- intChan :
            fmt.Printf("从intChan取出数据:%v\n", v)
        case v := <- strChan :
            fmt.Printf("从strChan取出数据:%s\n", v)
        default :
            fmt.Println("取不到数据了,程序员可以添加自己的逻辑代码")
            return
        }
    }
}

3.goroutine 中使用 recover 解决协程中出现 panic导致程序崩溃问题

        如果我们起了一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,就会造成整个程序崩溃,这时我们可以在 goroutine 中使用 recover来捕获 panic ,进行处理,这样即使这个协程发生的问题,但是主线程仍然不受影响,可以继续执行

package main

import (
    "fmt"
    "time"
)

func sayHello() {
    for i := 0; i < 10; i++ {
        time.Sleep(time.Second)
        fmt.Println("hello")
    }
}

func test()  {
    //这里可以使用defer + recover
    defer func ()  {
        //捕获test抛出的异常
        if err := recover(); err != nil {
            fmt.Println("test() 发生错误", err)
        }
    }()
    var map1 map[int]string
    map1[0] = "go"  //error
}

func main()  {
    go sayHello()
    go test()

    for i := 0; i < 10; i++ {
        fmt.Println("main hi")
        time.Sleep(time.Second)
    }
}

[上一节][go学习笔记.第十四章.协程和管道] 2.管道

[下一节]go学习笔记.第十五章.反射] 1.反射的基本介绍以及实践 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/8363.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里内部目前最完整“Spring全线笔记”,不止是全家桶,太完整了

前言 对于每一位Java开发人员来说&#xff0c;提起Spring定是不陌生的&#xff0c;实际上自Spring框架诞生以来&#xff0c;就备受开发者的青睐&#xff0c;基本上现在的互联网公司都要使用到Spring框架。Spring框架中又包含了SpringMVC、SpringBoot、SpringCloud等&#xff0…

【前端】Vue+Element UI案例:通用后台管理系统-项目总结

文章目录相关链接前言效果登录页首页管理员的首页xiaoxiao的首页用户管理总结项目搭建左侧&#xff1a;CommonAside上侧&#xff1a;CommonHeader和CommonTag首页&#xff1a;Home.vue用户管理&#xff1a;User.vue登录页&#xff1a;Login.vue总代码相关链接 参考视频&#x…

Spark 3.0 - 1.Spark 新特性简介与 WordCount Demo 实践

目录 一.引言 二.Spark 3.0 特性 1.Improving the Spark SQL engine [改进的SQL引擎] 1.1 Dynamic Partition Pruning [动态分区修剪] 1.2 ANSI SQL compliant [兼容 ANSI SQL] 1.3 Join hints [连接提示] 2.Enhancing the Python APIs: PySpark and Koalas [增强Python…

推荐国产神器Eolink!API优先,Eolink领先!

前言&#xff1a; 在我们后端开发者做项目的时候&#xff0c;避免不了与前端界面的交互&#xff0c;对于我来讲&#xff0c;在做项目的时候用到过postman&#xff0c;swagger做接口测试工作&#xff0c;在公司的时候公司主要用YApi可视化接口平台&#xff0c;最近使用了一个爆款…

Express

目录 Express介绍 测试最基本的服务器 Express基本使用 托管静态资源 2. 挂载路径前缀 nodemon 路由 模块化路由 2.注册使用路由模块 中间件的概念 ​编辑1.next函数的作用 2.app.use(全局中间件) 4.连续多个中间件 5.局部生效中间件 中间件的分类 1.应用级别…

自动驾驶入门:感知

目录 概念 感知方法 CNN 检测与分类 跟踪 分割 Apollo感知 感知设备分类 概念 我们人类天生就配备多种传感器&#xff0c;眼睛可以看到周围的环境&#xff0c;耳朵可以用来听&#xff0c;鼻子可以用来嗅&#xff0c;也有触觉传感器&#xff0c;甚至还有内部传感器&…

Android 性能优化

你会学到什么&#xff1f; 深入底层&#xff0c;全面建立性能优化知识体系&#xff1b; 高手思路&#xff0c;掌握大厂性能调优方法论&#xff1b; 三大模块&#xff0c;实战内存速度包体积优化&#xff1b; 玩转“黑科技”&#xff0c;轻松实现性能优化进阶。 作者介绍 赵…

现货黄金基本知识:黄金策略五大心法

我们经常看电视剧或小说&#xff0c;都会看到一些老套的情景&#xff0c;例如当某个人物死的时候&#xff0c;会讲毕生所学&#xff0c;或者是功夫、或者是知识&#xff0c;传到某一个人的手中。在现货黄金市场&#xff0c;也有累积了历代交易高手的“武功心法”&#xff0c;虽…

Bergsoft NextSuite (VCL) 不同的方式提供数据

Bergsoft NextSuite (VCL) 不同的方式提供数据 BergSoft NextSuite是一家强大的Delphi和CBuilder。NextGrid是一台易于使用的计算机&#xff0c;可以在设计时和设计时理解方法和技能。NextGrid有不止一个StringGrid和ListView美味的标准。NextDBGrid是基于著名NextGrid计算机的…

深入浅出PyTorch——基础知识

一、PyTorch的简介和安装 因为在学习pytorch之前就已经配置和安装好了相关的环境和软件&#xff0c;所以这里就不对第一章进行详细的总结&#xff0c;就简要总结一下&#xff1a; 1.1 pytorch的发展 去了Paper with code网站查看了现在pytorch的使用&#xff0c;远超tensor…

【机器学习】线性分类【下】经典线性分类算法

主要参考了B站UP主“shuhuai008”&#xff0c;包含自己的理解。 有任何的书写错误、排版错误、概念错误等&#xff0c;希望大家包含指正。 由于字数限制&#xff0c;分成两篇博客。 【机器学习】线性分类【上】广义线性模型 【机器学习】线性分类【下】经典线性分类算法 3. 线…

Spring知识点补充

1.常见的ORM框架都有哪些呢&#xff1f; 什么是ORM框架&#xff1f; 所谓的ORM框架&#xff0c;就是对象关系映射框架&#xff0c;就是让我们程序中的类里面的属性直接映射到我的数据库中的表里面的列&#xff0c;我们在Java中操作这个类的时候&#xff0c;就相当于直接操作数据…

Python避坑指南(续)

在上一篇《Python避坑指南》中&#xff0c;我重点给大家讲了Python可变容器数据类型中的坑。除了这些&#xff0c;Python还有其他一些细小方面的坑&#xff0c;本章为大家讲解Python中这些大家可能会忽视的细节。 文章目录链式or的坑访问字面量属性的坑is的坑GIL全局锁的坑多数…

建模杂谈系列177 APIFunc继续实践-比对研究

说明 在最终的实用上,我还是选择了Kettle。主要还是因为考虑未来公司的部署和使用上有比较全的文档,也比较有说服力。所以有时候也挺有趣的: 1 其实APIFunc要好得多,但是(刚做完原型验证)并不能取得大部分人的信任2 有一些方法对于有一定基础的人来说很方便,但是对于更…

剑指offer试题整理1

1、定义一个空的类型&#xff0c;里面没有任何成员变量和成员函数。对该类型求sizeof&#xff0c;得到的结果是什么&#xff1f; 答案&#xff1a;1. 为什么不是0? 空类型的示例中不包含任何信息&#xff0c;本来求siezof应该是0&#xff0c;但是当我们声明改类型的实列是时…

Zookeeper的数据模型和节点类型

数据模型&#xff1a; 树形结构 zk维护的数据主要有&#xff1a;客户端的会话&#xff08;session&#xff09;状态及数据节点&#xff08;dataNode&#xff09;信息。 zk在内存中构造了个DataTree的数据结构&#xff0c;维护着path到dataNode的映射以及dataNode间的树状层级关…

asp.net+sqlserver团购网站c#

数据需求分析 该网站的主要功能主要体现在对各种信息的添加、修改、删除和查询的操作上&#xff0c;包括会员信息、公司管理信息、订单信息、产品信息、团购管理信息等&#xff0c;各部分的信息之间又有着内在联系&#xff0c;因此总结出如下需求&#xff1a; &#xff08;1&am…

使去中心化媒体网络相关联的NFT元数据标准

1. 概述 (社交)媒体网络的力量日益强大。我们需要分散这种力量&#xff0c;使网络更加透明。 由于网络效应&#xff0c;新媒体网络和能够与现有网络竞争的去中心化替代方案很难吸引广大公众。 我们建议&#xff0c;与其创建新的协议&#xff0c;将每个平台的内容隔离起来&…

录屏怎么录,这2个方法不容错过!

​我们都知道在电脑使用频率越来越高的现在&#xff0c;无论是生活中还是工作中&#xff0c;有时可能会因为一些需要&#xff0c;使用到录屏的功能。最近&#xff0c;有不少的小伙伴前来询问小编&#xff0c;录屏怎么录&#xff1f;其实答案很简单&#xff0c;接下来小编分享的…

手机号码认证什么价格?手机号码认证怎样申请?

手机号码认证基于通话场景(呼入&#xff0c;呼出)的指定号码&#xff0c;来电展示企业号码专门名称&#xff0c;可提高电话号码辨识度&#xff0c;防止错误标记&#xff0c;提升品牌曝光度、接听率&#xff0c;低成本提升目标顾客接听率。 手机号码认证如何收费呢&#xff1f;…