golang atomic load 性能_golang 并发编程

并发是 golang 的优势之一,使用关键字 go 可以很方便的开启一个协程. go 语言中,常常用 go、chan、select 及 sync 库完成并发操作,处理同步、异步、阻塞、非阻塞任务.

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

make(chan int)make(chan int, 10)

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

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

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

var ch chan  

2.3 select

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

// 1. 超时机制select {  case 

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

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

示例:

ch := make(chan int, 1)ch 

3. 阻塞、同步与异步

3.1 阻塞与非阻塞

golang atomic load 性能_golang 并发编程_第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: 周期性定时器. 会按照初设定的时间重复计时.

示例:

// timerfor {  

5. 结语

5.1 思考题

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

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

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

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

5.2 参考资料

1. Golang 并发编程 [https://www.cnblogs.com/konghui/p/10703615.html#close]

2. 深入理解并发/并行,阻塞/非阻塞,同步/异步[https://cloud.tencent.com/developer/article/1339622]

你可能感兴趣的:(golang,atomic,load,性能)