细说 goroutine 和 channel

code · · 567 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

+  goroutine 看一个需求

 * 需求:要求统计 1 - 90000000000 的数字中,那些是素数?

+  分析思路:

 * 传统的方法,就是使用一个循环,循环的判断各个数是不是素数「效率很低」
 * 使用并发或者并行的方式,将统计素数的任务分配个多个goroutine去完成,这时就会使用到goroutine「速度提高很多」


 + goroutine 基本介绍
 
 > 进程和线程介绍
 
  * 进程就是程序程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单元
  * 线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单元
  * 一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行
  * 一个程序至少有一个进程,一个进程至少有一个线程

 + 并发和并行

  * 多线程程序在单核上运行,就是并发
  * 多线程程序在多核上运行,就是并行

 + 小结

  * 并发: 因为是在一个cpu上, 比如有10个线程,每个线程执行10ms(进行轮询操作),从人的角度看,好像这10个线程都在运行,但是从微观上看,
  在某个时间点看,其实只有一个线程在执行,这就是并发。

  * 并行: 因为是在多个cpu上(比如有10个cpu),比如有10个线程,每个线程执行10ms(各自在不同的cpu上执行),从人的角度看,这10个线程都在运行,但是从微观上看,在某个时间点看,也同时有10个线程在执行,这就是并行

 + go 协程和go主线程

  * go主线程(有程序员直接称为线程/也可以理解程进程) : 一个go线程上,可以起多个协程,你可以这里理解,协程是轻量级的线程「编译器做优化」
 
 + go 协程的特点

  * 有独立的栈空间
  * 共享程序堆空间
  * 调度由用户控制
  * 协程是轻量级的线程

+ channel (管道)-看个需求
 
  * 需求: 现在要计算1-200 的各个数的阶乘,并且把各个数的阶乘放入到map中,最后显示出来,要求使用goroutine完成

> 分析思路

  * 使用goroutine来完成,效率高,但是会出现并发/并行安全问题
  * 这里就提出了不同的goroutine如何通信的问题

> 代码实现

  * 使用goroutine 来完成(看看使用goroutine并发完成会出现什么问题? 然后我们去解决)
  * 在运行某个程序时,如何知道是否存在资源竞争问题,方法很简单,在编译程序时。增加一个参数。 -race即可
  * 代码实现见  goroutine 和 channel/goroutine_channel.go

  > 上面的案例就出现了资源的竞争的问题

+ 不同goroutine之间如何通讯

  * 全局变量的互斥锁
  * 使用管道channel来解决

+ 使用全局变量加锁同步改进程序

  * 因为没有对全局变量m加锁,因此会出现资源争夺问题,代码会出现错误,提示 fatal error: concurrent map writes
  * 解决方案: 加入互斥锁
  * 我们的数的阶乘很大,结果会越界,可以将求阶乘改成sum+=uint64(i)
  * 改进代码实现见  goroutine 和 channel/goroutine_channel_new.go

+ 为什么需要channel

  * 前面使用全局变量加锁同步来解决goroutine 的通讯,但不完美
  * 主线程在等待所有goroutine全部完成的时间很难确定,我们这里设置10s,仅仅是估算
  * 如果主线程休眠时间长了,会加长等待时间,如果时间短了,可能还有goroutine处于工作状态,这时也会随主线程的退出而销毁
  * 同步全局变量加锁同步来通讯,也不利用多个协程对全局变量的读写操作
  * 上面种种分析都在呼唤一个新的通讯机制- channel
 
+ channel 的基本介绍
 
  * channel本质就是一个数据结构-队列
  * 数据是先进先出(FIFO: first in first out)
  * 线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
  * channel有类型的,一个string的channel只能存放string类型数据
  * channel 是线程安全的,多个协程操作同一个管道时,不会发生资源竞争问题

+ 定义/声明channel

  * var 变量名 chan 数据类型
  * 举例:
      var intChan chan int (intChan 用户存放int数据)
      var mapChan chan map[int]string (mapChan 用户存放map[int]string类型)
      var perChan chan Person
      var PerChan2 chan *Person
      ....
   > 说明
    - channel 是引用类型
    - channel必须初始化才能写入数据,即make后才能使用
    - 管道是有类型的,intChan只能写入整型int   

+ 管道的初始化,写入数据到管道,从管道读取数据以及基本注意事项

  * 代码实现见  goroutine 和 channel/channel.go

+ channel 使用的注意事项

  * channel 中只能存放指定的数据类型
  * channel 的数据放满后,就不能再放入了
  * 如果从channel取出数据后,可以继续放入
  * 在没有使用协程的情况下,如果channel数据取完了,在取,就会报dead lock

+ 读写channel 案例演示

  * 案例见 goroutine 和 channel/intChan.go     
  * 案例见 goroutine 和 channel/mapChan.go 
  * 案例见 goroutine 和 channel/catChan.go 
  * 案例见 goroutine 和 channel/catChan2.go 

- channel 的遍历和关闭

+ channel的关闭
  
  * 使用内置函数close 可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从该channel读取数据
  * 案例演示见goroutine 和 channel/close.go

- channel 的遍历

+ channel支持for-range的方式进行遍历,请注意两个细节

  * 再遍历时,如果channel没有关闭,则会出现deadlock的错误。
  * 再遍历时。如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历

- channel遍历和关闭的案例演示

+ 代码演示 goroutine 和 channel/channel_for_close.go

- 应用实例

+ 请完成gorouteine 和 channel协同工作的案例。具体要求
  
  * 开启一个writeData协同,向管道intChan中写入50个整数
  * 开启一个readData协程,从管道intChan中读取writeData写入的数据
  * 注意: writeData 和 readData操作的是同一个管道
  * 主线程需要等待writeData 和 readData协程都完成工作才能退出[管道]
  * 案例代码  goroutine 和 channel/gorouteine.go


+ channel 使用细节和注意事项
  
  * channel可以声明为只读,或者只协性质
  案例 chan01.go
  * channel 只读和只写的最佳实践案例
   案例 chan02.go
  * 使用select可以解决从管道取数据的阻塞问题
  * goroutine 中使用recover,解决协程中出现panic,导致程序崩溃问题
  * 如果我们起一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,就会造成整个程序崩溃,这时我们
  可以再goroutine中使用recover来捕获panic,进行处理,这样即使这个协程发生的问题,但是主线程仍然不受影响,可以
  继续执行

.....详细见下面连接

细说 goroutine 和 channel


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:code

查看原文:细说 goroutine 和 channel

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

567 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传