goroutine
Go语言的特色之一就是支持协程。协程是一种轻量级的线程,其在操作系统中通常被称为用户态线程,因为它们是由用户程序自己实现的,而不是由操作系统内核实现的。与传统的线程相比,协程具有以下优点:占用资源少、切换成本低、并发操作高效。
在程序启动时,Go 运行时系统会创建一个主协程,该协程负责程序的初始化和启动。在程序运行过程中,通过 go
关键字可以创建新的协程。主协程和新协程可以并行执行,互不影响。
1 | func main(){ |
主协程
封装 main
函数的goroutine称为主goroutine
主goroutine会进行一系列的初始化工作:
- 创建一个特殊的
defer
语句,用于在主goroutine退出时做必要的善后处理。因为主goroutine也可能非正常的结束 - 启动专用于在后台清扫内存垃圾的goroutine,并设置GC可用的标识
- 执行
mian
包中的init
函数 - 执行
mian
函数
CSP(communicating sequential processes)并发模型
不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”
Go的CSP并发模型,是通过 goroutine 和 channel 来实现的
channel 是Go语言中各个并发结构体(goroutine)之间的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。传数据用 channel <- data
,取数据用 <-channel
1 | package main |
Context
- Context只有两个功能,跨API或者进程间:
- 携带键值对
- 传递取消信号(主动取消、时限/超时自动取消)
Context创建空上下文方式:
context.Background()
- context的默认值,所有的context都应该从它衍生出来context.TODO()
- 不确定要使用哪个上下文时,可以将其用作占位符
Context 有四个
with
系列函数进行派生:1
2
3
4func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
这四个函数都基于 父Context 衍生,通过这些函数,就创建了一颗Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个,如下图:
WithValue()
WithValue()
函数可以让Context实现携带键值对的功能。该函数传入的 Context 是 parent Context 。返回的 Context 与其是派生关系。(相当于上面 Context tree 的子节点)
例子:
1 | func main() { |
输出如下:
1 | k1 of b: v1 |
超时控制
withTimeout()
和 withDeadline()
来做超时控制,它俩的作用是一样的,就是传递的时间参数不同。它们都通过传入的时间来自动取消Context,要注意的是它们都会返回一个 cancelFunc
方法,通过调用这个方法可以达到提前进行取消,不过在使用的过程还是建议在自动取消后也调用 cancelFunc
去停止定时减少不必要的资源浪费。
1 | func main() { |
输出结果如下:
1 | deal time is 0 |
WithCancel()
日常业务开发中往往为了完成一个复杂的需求会开多个gouroutine去做一些事情,这就导致一次请求中开了多个goroutine却无法控制他们,这时我们就可以使用 withCancel
来衍生一个context传递到不同的goroutine中,当我想让这些goroutine停止运行,就可以调用 cancel
来进行取消。
Done()
Done()
函数确认上下文是否完成,无论上下文是因为什么原因结束的,都可以通过调用其 Done()
方法确认:该方法会返回一个通道(chan),该通道会在上下文完成时被关闭,任何监听该通道的函数都会感应到对应上下文完成的事件。
- 没有关闭的时候,
case <- ctx.Done()
会阻塞住 - 关闭之后,每次
<- ctx.Done()
都会返回一个零值
1 | func main() { |
输出结果如下:
1 | balabalabalabala |
`Err()``
Err()
函数返回上下文结束的原因,如果上下文没有结束,返回 nil
,如果上下文已经结束,获取上下文错误