golang并发之Goroutine & channel完全解读

目录

0、几个概念

1、一个通道发送和接收数据,默认是阻塞的。

2、关闭通道和通道上范围循环

(1)关闭通道

(2)通道上范围循环遍历

3、缓冲通道

(1)非缓冲通道

(2)缓冲通道

4、定向通道

(1)单向 channel 变量的声明

(2)time包中的单向通道相关函数

5、Select语句

6、CSP并发模型

7、并发编程实例

(1)请完成goroutine和channel协调工作的案例

(2)统计素数

(3)计算累加和


0、几个概念

(1)进程/线程

  • 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。
  • 线程是进程的一个执行实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位
  • 一个进程可以创建和撤销多个线程,同一个进程中的多个线程之间可以并发执行。

(2)并发/并行

  • 多线程程序在单核的 cpu 上运行,称为并发;多线程程序在多核的 cpu 上运行,称为并行。
  • 并发与并行并不相同,并发主要由切换时间片来实现“同时”运行,并行则是直接利用多核实现多线程的运行,Go程序可以设置使用核心数,以发挥多核计算机的能力。

(3)协程/线程

  • 协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
  • 线程:一个线程上可以跑多个协程,协程是轻量级的线程
  • 优雅的并发编程范式,完善的并发支持,出色的并发性能是Go语言区别于其他语言的一大特色。使用Go语言开发服务器程序时,就需要对它的并发机制有深入的了解。

(4)Goroutine

  • goroutine 是一种非常轻量级的实现,可在单个进程里执行成千上万的并发任务,它是Go语言并发设计的核心。
  • 说到底 goroutine 其实就是线程,但是它比线程更小,十几个 goroutine 可能体现在底层就是五六个线程,而且Go语言内部也实现了 goroutine 之间的内存共享。
  • 使用 go 关键字就可以创建 goroutine,将 go 声明放到一个需调用的函数之前,在相同地址空间调用运行这个函数,这样该函数执行时便会作为一个独立的并发线程,这种线程在Go语言中则被称为 goroutine。

(5)channel

  • channel 是Go语言在语言级别提供的 goroutine 间的通信方式。可以使用 channel 在两个或多个 goroutine 之间传递消息

1、一个通道发送和接收数据,默认是阻塞的。

  • 如果只有写数据,会发生死锁;如果只有读数据,也会发生死锁。
  • 在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报fatal error: all goroutines are asleep - deadlock!
  • 当我们给管道写入数据时,不能超过其容量

2、关闭通道和通道上范围循环

使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从该channel读取数据

(1)关闭通道

close的目的就是用来通知对方这个通道是关闭的,以此来结束循环

关闭 channel 非常简单,直接使用 Go语言内置的 close() 函数即可:

close(ch)

在介绍了如何关闭 channel 之后,我们就多了一个问题:如何判断一个 channel 是否已经被关闭?我们可以在读取的时候使用多重返回值的方式:

x, ok := <-ch

这个用法与 map 中的按键获取 value 的过程比较类似,只需要看第二个 bool 返回值即可,如果返回值是 false 则表示 ch 已经被关闭。

(2)通道上范围循环遍历

管道不能使用普通的for循环

在遍历时,如果channel没关闭,则会出现deadlock的错误:fatal error: all goroutines are asleep - deadlock!

在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历

3、缓冲通道

(1)非缓冲通道

以上介绍的通道都没有缓冲,发送和接收到一个未缓冲的通道是阻塞的。

一次发送操作对应一次接收操作,对于一个goroutine来讲,它的一次发送,在另一个goroutine接收之前都是阻塞的。同样的,对于接收来讲,在另一个goroutine发送之前,它也是阻塞的

(2)缓冲通道

缓冲通道就是指一个通道带有缓冲区。发送到一个缓冲通道只有在缓冲区满时才被阻塞。类似地,从缓冲通道接收的信息只有在缓冲区为空时才会被阻塞。带缓冲通道在很多特性上和无缓冲通道是类似的。无缓冲通道可以看作是长度永远为 0 的带缓冲通道。

因此根据这个特性,带缓冲通道在下面列举的情况下依然会发生阻塞

  • 带缓冲通道被填满时,尝试再次发送数据时发生阻塞。
  • 带缓冲通道为空时,尝试接收数据时发生阻塞。

如何创建带缓冲的通道呢——通道实例 := make(chan 通道类型, 缓冲大小)

  • 通道类型:和无缓冲通道用法一致,影响通道发送和接收的数据类型。
  • 缓冲大小:决定通道最多可以保存的元素数量。
  • 通道实例:被创建出的通道实例。

为什么Go语言对通道要限制长度而不提供无限长度的通道?

我们知道通道(channel)是在两个 goroutine 间通信的桥梁。使用 goroutine 的代码必然有一方提供数据,一方消费数据。当提供数据一方的数据供给速度大于消费方的数据处理速度时,如果通道不限制长度,那么内存将不断膨胀直到应用崩溃。因此,限制通道的长度有利于约束数据提供方的供给速度,供给数据量必须在消费方处理量+通道长度的范围内,才能正常地处理数据。

4、定向通道

我们在将一个 channel 变量传递到一个函数时,可以通过将其指定为单向 channel 变量,从而限制该函数中可以对此 channel 的操作,比如只能往这个 channel 写,或者只能从这个 channel 读。

(1)单向 channel 变量的声明

单向 channel 变量的声明非常简单,只能发送的通道类型为chan<-,只能接收的通道类型为<-chan,格式如下:

  • var 通道实例 chan<- 元素类型    // 只能发送通道
  • var 通道实例 <-chan 元素类型    // 只能接收通道

(2)time包中的单向通道相关函数

time 包中的计时器会返回一个 timer 实例,代码如下:

timer := time.NewTimer(time.Second)

timer的Timer类型定义如下:

type Timer struct {
  C <-chan Time
  r runtimeTimer
}

第 2 行中 C 通道的类型就是一种只能接收的单向通道。如果此处不进行通道方向约束,一旦外部向通道发送数据,将会造成其他使用到计时器的地方逻辑产生混乱。因此,单向通道有利于代码接口的严谨性

NewTimer和After方法差不多,只是返回值不同;After直接返回通道

5、Select语句

select 的用法与 switch 语言非常类似,由 select 开始一个新的选择块,每个选择条件由 case 语句来描述。与 switch 语句相比,select 有比较多的限制,其中最大的一条限制就是每个 case 语句里必须是一个 IO 操作,select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到case可运行。

在一个 select 语句中,Go语言会按顺序从头至尾评估每一个发送和接收的语句。
如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。
如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有如下两种可能的情况:

  • 如果给出了 default 语句,那么就会执行 default 语句,同时程序的执行会从 select 语句后的语句中恢复;
  • 如果没有 default 语句,那么 select 语句将被阻塞,直到至少有一个通信可以进行下去。

6、CSP并发模型

CSP 并发模型是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享 channel(管道)进行通信的并发模型。但是Go语言并没有完全实现了 CSP 并发模型的所有理论,仅仅是实现了 process 和 channel 这两个概念。process 就是Go语言中的 goroutine,每个 goroutine 之间是通过 channel 通讯来实现数据共享。

7、并发编程实例

(1)请完成goroutine和channel协调工作的案例

具体要求:

  • 开启一个writeData协程,向管道intChan中写入50个整数
  • 开启一个readChan协程,从管道中读取writeData写入的数据
  • 注意:writeData和readChan操作的是同一个管道
  • 主线程需要等待writeData和readData协程都完成工作才能退出【管道】

以下两种写法均可

   

或者:

   

(2)统计素数

  • 要求统计1-20000的数字中,哪些是素数?将统计素数的任务分给4个协程去完成

(3)计算累加和

  • 启动一个协程,将1-2000的数放到一个channel中,比如numChan
  • 启动8个协程,从numChan取出数字(比如n),并计算1+2+……+n的值,并存放到resChan
  • 最后8个协程同完成工作后,再遍历resChan,显示结果【如res[1]=1……res[10]=55……】

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值