您的位置 首页 golang

golang 并发编程

1. 概要

go 语言的并发编程,以下是需要了解的基础知识点,也是本文主要介绍的内容. 可以对照看看这些是否已经可以熟练运用了.

  • 阻塞 : 阻塞是进程(也可以是 线程 、协程)的状态之一(新建、就绪、运行、阻塞、终止). 指的是当数据未准备就绪,这个进程(线程、协程)一直等待,这就是阻塞.
  • 非阻塞 : 当数据为准备就绪,该进程(线程、协程)不等待可以继续执行,这就是非阻塞.
  • 同步 : 在发起一个调用时,在没有得到结果之前,这个调用就不返回,这个调用过程一直在等待. 这是同步.
  • 异步 : 在发起调用后,就立刻返回了,这次调用过程就结束了. 等到有结果了被调用方主动通知调用者结果. 这是异步.
  • go(协程) : 通过关键字 go 即可创建一个协程.
  • chan : golang 中用于并发的通道,用于协程的通信.
    • 有缓冲通道
    • 无缓冲通道
    • 单向通道
  • select : golang 提供的多路复用机制.
  • close () : golang 的内置函数, 可以关闭一个通道.
  • sync : golang 标准库之一,提供了锁.
  • 定时器 : golang 标准库 time 提供的重要功能, 提供了定时器功能,可用于超时处理.
    • Timer
    • Ticker

2. go 并发编程

go 的并发编程采用的 CSP (Communicating Sequential Process) 模型,主要基于协程 goroutine 和通道 channel .

2.1 协程 go

在 go 语言中,并发编程使用关键字 go 即可快速启动一个并发运行的 goroutine. 如下:

 go 函数名 (参数列表)
go f(a int, b int, c int){
  fmt.Println(a+b+c)
}(1,2,3)  

2.2 channel 通道

golang 提供了通道类型 ch an,用于在并发操作时的通信,它本身就是并发安全的. 通过 chan 可以创建无缓冲、缓冲通道,满足不同需求. 写法如下:

 make(chan int)
make(chan int, 10)
<- chan
chan <-  

无缓冲通道: 要求接受和发送数据的 goroutine 同时准备好,否则将会阻塞.

有缓冲通道: 给予通道一个容量值,只要有值便可以接受数据,有空间便可以发送数据,可以不阻塞的完成.

单向通道 : 默认情况通道是双向的,可以接受及发送数据. 也可以创建单向通道,只能收或者发数据. 如下是单向接受通道

 var ch chan
  <- float64  

2.3 select

select: 可以监听 channel 上的输入/输出操作, 类似于 select、epoll、poll 使得通道支持多路复用. select 是专门通道 channel 设计的. 它可以结合通道实现 超时处理、判断缓冲通道是否阻塞、退出 信号量 处理 ,如下:

 // 1. 超时机制
select {
  case <-ch:
  case <-timeout:
  fmt.Println("timeout 01")}​// 2. 退出信号量处理select {    case <- quitChan:        return    default:}​// 3. 判断缓冲通道是否已满ch := make(chan int, 5)ch <- 1select {    case ch <- 2:        fmt.Println("channel value is", <-ch)    default:        fmt.Println("channel blocking")}  

2.4 内置函数 close() close() 函数用于关闭通道 channel 的,close 之后的 channel 还可以读取数据,close() 函数由以下几点使用要点:

  1. 只能关闭双向通道或者发送通道
  2. 它应该由发送者使用,而不应该由接受者调用
  3. 当通道关闭后,接受者都不再阻塞,
  4. 关闭通道后,依然可以从通道中读取值
  5. 所有元素读取完后,将返回通道元素的零值,并且读取检测值也是 false

示例:

 ch := make(chan int, 1)
ch <- 3
close(ch) // 关闭ch
v, ok := <- ch // 3,true
v2,ok := <- ch // 0,false  

3. 阻塞、同步与异步

3.1 阻塞与非阻塞

阻塞 : 阻塞是进程(也可以是线程、协程)的状态之一(新建、就绪、运行、阻塞、终止). 指的是当数据未准备就绪,这个进程(线程、协程)一直等待,这就是阻塞.

非阻塞 : 当数据为准备就绪,该进程(线程、协程)不等待可以继续执行,这就是非阻塞.

3.2 同步与异步

同步 : 在发起一个调用时,在没有得到结果之前,这个调用就不返回,这个调用过程一直在等待. 这是同步.

异步 : 在发起调用后,就立刻返回了,这次调用过程就结束了. 等到有结果了被调用方主动通知调用者结果. 这是异步.

3.3 四种组合

同步、异步、阻塞、非阻塞可以组合成四种并发方式:

  • 同步阻塞调用 :得不到结果不返回,线程进入阻塞态等待。
  • 同步非阻塞调用 :得不到结果不返回,线程不阻塞一直在CPU运行。
  • 异步阻塞调用 :去到别的线程,让别的线程阻塞起来等待结果,自己不阻塞。
  • 异步非阻塞调用 :去到别的线程,别的线程一直在运行,直到得出结果。

4. 锁与定时器

4.1 锁与 sync 库

并发编程中,为了确保并发安全,可以使用锁机制. golang 提供了标准库 sync ,它实现了并发需要的各种锁. 包括:

  • Mutex : 互斥锁,有俩个方法 Lock() Unlock() , 它只能同时被一个 goroutine 锁定,其它锁再次尝试锁定将被阻塞,直到解锁.
  • RWMutex : 读写锁,有四个方法, Lock() 写锁定、 Unlock() 写解锁、 RLock() 读锁定、 RUnlock() 读解锁,读锁定和写锁定只能同时存在一个. 只能有一个协程处于写锁定状态,但是可以有多个协程处于读锁定状态. 即写的时候不可读,读的时候不可写. 只能同时有一个写操作确保数据一致性. 而可以多个协程同时读数据,确保读操作的并发性能.

此外在 go 的并发编程中,还会常用到 sync 的以下内容:

  • sync.Map : 并发安全的字典 map
  • sync.WaitGroup : 用来等待一组协程的结束,常常用来阻塞主线程.
  • sync.Once : 用于控制函数只能被使用一次,
  • sync.Cond : 条件同步变量. 可以通过 Wait()方法阻塞协程,通过 Signal()、Broadcast() 方法唤醒协程.
  • sync.Pool : 一组临时对象的集合,是并发安全的. 它主要是用于存储分配但还未被使用的值,避免频繁的重新分配内存,减少 gc 的压力.

3.2 time 库的定时器

golang 的标准库 time 中提供了定时器功能,并提供通道 channel 变量进行定时通知. time 库中提供了两种定时器:

  • time.Timer: 定时器 timer 在创建指定时间后,向通道 time.Timer.C 发送数据. 之后需要使用 Reset 设定定时器时间.
  • time.Ticker: 周期性定时器. 会按照初设定的时间重复计时.

示例:

 // timer
for {
  <- timer.C
  timer.Reset(time.Second)
  // 重设后才有效
}

// ticker
for {
  <- ticker.C // 周期性有效
}  

5. 结语

5.1 思考题

1. golang 中 select 的多个 case 同时成立,那么选择的是哪一个?

2. golang 中除了使用 sync 锁,还可以如何保证并发安全? atomic 是什么?

3. sync.Map 对键的类型有什么要求么?

4. 如何避免 死锁 ? golang 中如何检测死锁?

5.2 参考资料

1. Golang 并发编程 [#close]

2. 深入理解并发/并行,阻塞/非阻塞,同步/异步[

文章来源:智云一二三科技

文章标题:golang 并发编程

文章地址:https://www.zhihuclub.com/86287.shtml

关于作者: 智云科技

热门文章

网站地图