context.Context是Go语言中独特的设计,在其他编程语言中我们很少见到类似的概念。context.Context深度支持Golang的高并发。
1. Goroutine和Channel
在理解context包之前,应该首先熟悉Goroutine和Channel,能加深对context的理解。
1.1 Goroutine
Goroutine是一个轻量级的执行线程,多个Goroutine比一个线程轻量,所以管理Goroutine消耗的资源相对更少。Goroutine是Go中最基本的执行单元,每一个Go程序至少有一个Goroutine:主Goroutine。程序启动时会自动创建。为了能更好的理解Goroutine,先来看一看线程Thread与协程Coroutine的概念。
- 线程(Thread)
线程是一种轻量级进程,是CPU调度的最小单位。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属于一个进程的其他线程共享进程所拥有的全部资源。线程拥有自己独立的栈和共享的堆,共享堆,不共享栈。 线 程 的 切 换 一 般 由 操 作 系 统 调 度 \color{red}{线程的切换一般由操作系统调度} 线程的切换一般由操作系统调度。 - 协程(Coroutine)
协程又称为微线程,与子例程一样,协程也是一种程序组建,相对子例程而言,协程更为灵活,但在实践中使用没有子例程那样广泛。和线程类似,共享堆,不共享栈, 协 程 的 切 换 一 般 由 程 序 员 在 代 码 中 显 式 控 制 \color{red}{协程的切换一般由程序员在代码中显式控制} 协程的切换一般由程序员在代码中显式控制。他避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。
Goroutine和其他语言的协程(coroutine)在使用方式上类似,但从字面意义上来看不同(一个是Goroutine,一个是coroutine),再就是协程是一种协作任务控制机制,在最简单的意义上,协程不是并发的,而Goroutine支持并发的。因此Goroutine可以理解为一种Go语言的协程。同时,Gorotine可以运行在一个或多个线程上。
1.2 使用Goroutine示例
func Hello() {
fmt.Println("hello everybody , I'm lineshen")
}
func main() {
go Hello()
fmt.Println("Golang-Gorontine Example")
}
在该代码片段中,使用go又开启了一个Goroutine执行Hello方法,其运行结果如下:
Golang-Gorontine Example
从执行结果上,直观的看,我们的程序似乎没有执行Goroutine的Hello方法。出现这个问题的原因是我们启动的主Goroutine在main执行完就退出了,所以为了main等待Hello-Goroutine执行完,就需要一些方法,让Hello-Goroutine告诉main执行完了,这里就需要通道Channel了。
1.3 Channel
Channel就是多个Goroutine 之间的沟通渠道。当我们想要将结果或错误,或任何其他类型的信息从一个 goroutine 传递到另一个 goroutine 时就可以使用通道。通道是有类型的,可以是 int 类型的通道接收整数或错误类型的接收错误等。
假设有个 int 类型的通道 ch,如果想发一些信息到这个通道,语法是 ch <- 1,如果想从这个通道接收一些信息,语法就是 var := <-ch。这将从这个通道接收并存储值到 var 变量。
通过改善1.2中的代码片段,证明通道的使用确保了 goroutine 执行完成并将值返回给 main 。
func Hello(ch chan int) {
fmt.Println("hello everybody , I'm lineshen")
ch <- 1
}
func main() {
ch := make(chan int)
go Hello(ch)
<-ch
fmt.Println("Golang-Gorontine Example")
}
执行结果如下:
hello everybody , I'm lineshen
Golang-Gorontine Example
这里我们使用通道进行等待,这样main就会等待Hello-Goroutine执行完。熟悉了Goroutine、Channel的概念了,就很好理解Context了。
2. Context应用场景
熟悉了Goroutine、Channel的概念,先看一个请求服务场景示例:
func main() {
http.HandleFunc("/", SayHello) // 设置访问的路由
log.Fatalln(http.ListenAndServe(":8080",nil))
}
func SayHello(writer http.ResponseWriter, request *http.Request) {
fmt.Println