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

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

分析:

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

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

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

正常的代码:

package main

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

运行结果:会报错

1.利用互斥锁 

package main

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

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

package main

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

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

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

channel的关闭:

使用内置的close可以关闭管道,关闭后不能再进行写入,但是可以进行读取;

channel的遍历:

channel可以使用for range进行遍历 ,但是要注意:

  • 在遍历时,如果channel没有关闭,则会出现deadlock错误;
  • 在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完成后退出;(即在遍历前需要先关闭管道)

2.利用管道实现边写边读

流程图:

package main

import (
"fmt"
) var (
myMap = make(map[int]int, )
) func cal(n int) map[int]int {
res :=
for i := ; i <= n; i++ {
res *= i
}
myMap[n] = res
return myMap
} func write(myChan chan map[int]int) {
for i := ; i <= ; i++ {
myChan <- cal(i)
fmt.Println("writer data:", cal(i))
}
close(myChan)
} func read(myChan chan map[int]int, exitChan chan bool) {
for {
v, ok := <-myChan
if !ok {
break
}
fmt.Println("read data:", v)
}
exitChan <- true
close(exitChan)
} func main() {
var myChan chan map[int]int
myChan = make(chan map[int]int, )
var exitChan chan bool
exitChan = make(chan bool, )
go write(myChan)
go read(myChan, exitChan)
for {
_, ok := <-exitChan
if !ok {
break
}
} }

结果:

思考:假设我们注销掉go read(myChan,exitChan)会发生什么呢?

也就是说,只有写入myChan而没有读取myChan,当存入myChan里面的数据达到了myChan的容量,再继续存入就会报deadlock错误。同时,由于exitChan需要写入一个true,而exitChan需要读取完myChan中的数据后才写入一个true,但是现在不能进行读取,也就是说,true不会写入exitChan,就形成了阻塞。假设我们打开go read(myChan,exitChan),我们设置其每隔1秒才读取一条数据,而写入则让其正常运行,也就是说,写入很快,读取很慢,这样会导致deadlock吗?答案是不会,只要有读取,golang会有个机制,不会让myChan存储的值超过myChan的容量。

管道的使用注意事项:

(1)在默认情况下,管道是双向的。管道是可以声明是只读还是只写;

  var intChan chan<-int(只写)

  intChan = make(chan int,3)

   var intChan2 <-chan int

(2)使用select可以解决从管道取数据阻塞问题;

func Test2() {

    intChan := make(chan int, )
for i := ; i < ; i++ {
intChan <- i
}
strChan := make(chan string, )
for i := ; i < ; i++ {
strChan <- "hello" + fmt.Sprintf("%d", i)
}
//传统方法是可用close关闭,但是当不知道什么时候需要关闭时,这就不可用
//实际开发中可以使用select解决
for {
select {
case v := <-intChan:
fmt.Printf("从intChan中读取数据%d\n", v)
case v := <-strChan:
fmt.Printf("从strChan中读取数据%s\n", v)
default:
fmt.Println("都取不到数据了")
return
}
} }

运行结果:

(4)goroutine中使用recover,解决协程中出现panic,导致程序崩溃问题。

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

package main

import (
"fmt"
"time"
) func sayHello() {
for i := ; i < ; i++ {
time.Sleep(time.Millisecond * )
fmt.Println("hello")
} } func test() {
//这里我们可以使用defer revover解决nil
defer func() {
if err := recover(); err != nil {
fmt.Println("test()发生错误,error=", err)
}
}()
var myMap map[int]string
myMap[] = "golang"
}
func main() {
go sayHello()
go test()
for i := ; i < ; i++ {
time.Sleep(time.Millisecond * )
fmt.Println("main() ok=", i) }
}

运行结果:

(四十二)golang--管道的更多相关文章

  1. NeHe OpenGL教程 第四十二课:多重视口

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  2. 网站开发进阶(四十二)巧用clear:both

    网站开发进阶(四十二)巧用clear:both 前言 我们在制作网页中用div+css或者称xhtml+css都会遇到一些很诡异的情况,明明布局正确,但是整个画面却混乱起来了,有时候在IE6下看的很正 ...

  3. Gradle 1.12用户指南翻译——第四十二章. Announce插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  4. SQL注入之Sqli-labs系列第四十一关(基于堆叠注入的盲注)和四十二关四十三关四十四关四十五关

    0x1普通测试方式 (1)输入and1=1和and1=2测试,返回错误,证明存在注入 (2)union select联合查询 (3)查询表名 (4)其他 payload: ,( ,( 0x2 堆叠注入 ...

  5. “全栈2019”Java第四十二章:静态代码块与初始化顺序

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  6. 第四十二个知识点:看看你的C代码为蒙哥马利乘法,你能确定它可能在哪里泄漏侧信道路吗?

    第四十二个知识点:看看你的C代码为蒙哥马利乘法,你能确定它可能在哪里泄漏侧信道路吗? 几个月前(回到3月份),您可能还记得我在这个系列的52件东西中发布了第23件(可以在这里找到).这篇文章的标题是& ...

  7. abp(net core)+easyui+efcore实现仓储管理系统——入库管理之六(四十二)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  8. Dynamic CRM 2013学习笔记(四十二)流程5 - 实时/同步工作流(Workflow)用法图解

    实时工作流跟插件一样,也是用事件执行管道来执行,能在pre,post或核心操作中执行.跟插件一样,不能在创建之前和删除之后执行.如果执行过程中有异常发生,会取消并回滚整个操作.实时工作流里所有的活动和 ...

  9. Deep learning:四十二(Denoise Autoencoder简单理解)

    前言: 当采用无监督的方法分层预训练深度网络的权值时,为了学习到较鲁棒的特征,可以在网络的可视层(即数据的输入层)引入随机噪声,这种方法称为Denoise Autoencoder(简称dAE),由Be ...

  10. javaweb学习总结(四十二)——Filter(过滤器)学习

    一.Filter简介 Filter也称之为过滤器,它是Servlet技术中最激动人心的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态 ...

随机推荐

  1. H5 video踩坑实录

    前段时间公司APP做了一个APP论坛会议,嵌入了h5播放器.我以为很简单,没想到,这正是我踩进泥潭的开始... (想要吸取经验的小伙伴可以慢慢往下看,想要解决方案的直接看最后!) 一.一开始我以为直接 ...

  2. 编程语言分类 + python解释器的安装 + jupyter的使用(day 02整理)

    目录 一.昨日内容回顾 一 计算机基础之编程 (一) 什么是编程语言 (二) 什么是编程 (三) 为什么编程 二 计算机组成 (一) CPU (二) 存储器 (三) I/O设备 (四) 多核CPU ( ...

  3. 面向对象之---this的用法

    在绝大多数情况下,函数的调用方式决定了this的值 全局环境 无论是否在严格模式下,在全局执行环境中,this都指向全局对象· 在全局作用域中调用一个函数时,this总是指向Global对象(在浏览器 ...

  4. 消息中间件-RabbitMQ环境搭建

    一直在传统行业工作(早九晚五不加班),没有考虑消息中间件的性能,所以一直再用activeMQ也没有想过学习别的中间件,时间长也没什么技术上的进步,而且感觉到了 工作的麻木,所以决定学一些新的技术(其实 ...

  5. springcloud之Feign、ribbon设置超时时间和重试机制的总结

    一 超时时间配置 如果在一个微服务当中对同一个接口同时配置了Hystrix与ribbon两个超时时间,则在接口调用的时候,两个计时器会同时读秒. 比如,访问一个接口需要2秒,你的ribbon配置的超时 ...

  6. 在线API文档管理工具Simple doc

    Simple doc是一个简易的文档发布管理工具,为什么要写Simple doc呢?主要原因还是github的wiki并不好用:没有目录结构,文章没有Hx标签索引,最悲剧的是文章编辑的时候不能直接图片 ...

  7. C语言1博客作业04

    问题 答案 这个作业属于那个课程 C语言程序设计1 这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/CST2019-2/homework/9770 我在这 ...

  8. JavaSE语法

    二.JavaSE语法(上) 1.Java 有没有 goto 语句? goto 是 Java 中的保留字,在目前版本的 Java 中没有使用.根据 James Gosling(Java 之父)编写的&l ...

  9. PyCharm使用正则替换python中的静态资源

    python每次开发前台页面时,最无法避免的就是前台静态资源地址的替换了,手动替换成{% static 'web/.......' %}可想而知的痛苦,把正则替换的方式分享给朋友们,希望可以帮助到需要 ...

  10. kubernetes 控制器详解【持续完善中】

    目录 资源创建详解 一:Pod及常用参数 1.简介 2.模板 3.删除pod 4.设置Pod主机名 5.镜像拉取策略(ImagePullPolicy) 二:RC 1.简介 2.模板 三:Deploym ...