一.goroutine
1.1进程、线程、协程
进程(Process):在内存中的程序。有自己独立的独占的虚拟 CPU 、虚拟的 Memory、虚拟的 IO devices。
OS 直接支持并调度。进程之间只能通过系统提供的 IO 机制通讯。共享内存(变量)是不可能的!
- 每一进程占用独立的地址空间。
此处的地址空间包括代码、数据及其他资源。 - 进程间的通信开销较大且受到许多限制。
对象(或函数)接口、通信协议、… - 进程间的切换开销也较大。
又称Context Switch。
上下文包括代码、数据、堆栈、处理器状态、资源、…
注:上下文的定义:每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。
线程(Thread):轻量级进程。在现代操作系统中,是进程中程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
一个进程由若干线程组成,它们共享进程的计算、存储、IO资源。因此,程序员必须使用系统提供的同步、消息机制,处理资源的竞争和消息的通讯。
- 多个线程共享进程的地址空间(代码、数据、其他资源等)。
线程也需要自己的资源,如程序计数器、寄存器组、调用栈等。 - 线程间的通信开销较少且比较简单。
因为共享而减少了需要通信的内容。
但也因为充分共享而无法对共享资源进行保护。 - 线程间的切换开销也较小。
只需保存每一线程的程序计数器、寄存器组、堆栈等空间。
不必切换或复制整个地址空间,从而成本大为降低(约1/10)
线程有分为两大类:
- 操作系统管理的线程(Core Thread),通常根据 CPU 资源决定线程的数量,一般为 CPU 数量的两倍。
- 语言提供的线程库管理的线程(User Thread),它执行时映射到系统线程,按任务类型(计算密集型,IO密集型)决定线程池的管理方式与数量。
协程(coroutine/fiber):轻量级线程。 是可以并发执行的函数,由编译或用户指定位置将控制权交给协程调度程序执行的方式。它是非抢占式的,可以避免反复系统调用,还有进程切换造成的开销,给你上几千个逻辑流,也称用户级别线程。
在单线程模式下,协程不需要自己上下文,可以大大减少资源竞争的情况。例如,读写map的项时,不需要锁整个表。在 JavaScript、python等单进程单线程、数据驱动(流式)的应用中,协程比线程更有效率;结合回调函数,更高效的处理 IO 请求。
1.2 goroutine很像线程,但它占用的内存远小于线程,使用它需要的代码更少,goroutine是可以与其他goroutine并行执行的函数,同时也会与主程序并行执行,go语言中会使用同一个线程来执行多个goroutine。在go语言中,net/http库直接使用内置的goroutine,每个接收到的请求都自动在其自己的gorotine里处理,go语言运行时会自动在配置的一组逻辑处理器上调度执行goroutine。每个逻辑处理器绑定到一个操作系统的线程上。
二.channel
channel 是 goroutine 之间通信的一种方式,可以类比成 Unix 中的进程的通信方式管道。可以让用户在不同的goroutine之间同步发送具有类型的消息,这让编程模型更倾向于在goroutine之间发送消息,而不是让多个goroutine争夺同一个数据的使用权。
1.channel创建
channel 使用内置的 make 函数创建,下面声明了一个 chan int 类型的 channel:
ch := make(chan int)
c和 map 类似,make 创建了一个底层数据结构的引用,当赋值或参数传递时,只是拷贝了一个 channel 引用,指向相同的 channel 对象。和其他引用类型一样,channel 的空值为 nil 。使用 == 可以对类型相同的 channel 进行比较,只有指向相同对象或同为 nil 时,才返回 true
channel 的读写操作
ch := make(chan int)
// write to channel
ch <- x
// read from channel
x <- ch
// another way to read
x = <- ch
channel 一定要初始化后才能进行读写操作,否则会永久阻塞。
关闭 channel
golang 提供了内置的 close 函数对 channel 进行关闭操作。
ch := make(chan int)
close(ch)
- 关闭一个未初始化(nil) 的 channel 会产生 panic
- 重复关闭同一个 channel 会产生 panic
- 向一个已关闭的 channel 中发送消息会产生 panic
- 从已关闭的 channel 读取消息不会产生 panic,且能读出 channel 中还未被读取的消息,若消息均已读出,则会读到类型的零值。从一个已关闭的 channel 中读取消息永远不会阻塞,并且会返回一个为 false 的 ok-idiom,可以用它来判断 channel 是否关闭
- 关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息