进程是应用程序的启动实例,进程拥有代码和打开的文件资源、数据资源、独立的内存空间。线程是最小的执行单元。
线程从属于进程,是程序的实际执行者,一个进程至少包含一个主线程,也可以有更多的子线程,线程拥有自己的栈空间。进程是最小的资源管理单元。
协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。
协程和进程、线程不同,不是被操作系统内核所管理的,而是完全由程序所控制(在用户态执行)。因为不会像线程切换那样消耗资源,性能大幅度的提升。
协程是一个特殊的函数,这个函数可以在某个地方挂起,并且可以重新在挂起处外继续运行。协程与进程、线程相比并不是一个维度的概念。
一个进程可以包含多个线程,一个线程也可以包含多个协程。一个线程内可以由多个这样的特殊函数在运行,但是有一点必须明确的是,一个线程的多个协程的运行是串行的。如果是多核CPU,多个进程或一个进程内的多个线程是可以并行运行的,但是一个线程内协程却绝对是串行的,无论CPU有多少个核。当一个协程运行时,其它协程必须挂起。
本质上,goroutine 就是协程。
不同的是,Golang 在 runtime、系统调用等多方面对 goroutine 调度进行了封装和处理,当遇到长时间执行或者进行系统调用时,会主动把当前 goroutine 的CPU (P) 转让出去,让其他 goroutine 能被调度并执行,也就是 Golang 从语言层面支持了协程。
Golang 的一大特色就是从语言层面原生支持协程,在函数或者方法前面加 go关键字就可创建一个协程。
import (
"fmt"
"time"
)
func main(){
for i:=0; i<10; i++ {
go func(i int) {
fmt.Printf("run in child coroutine %d.\n", i)
}(i)
}
// 防止子协程还没有结束主协程就退出了
time.Sleep(time.Second * 1)
}
在GO里不同的并行协程之间交流的方式有两种,一种是通过共享变量,另一种是通过通道。Go 语言鼓励使用通道的形式来交流。
c := make(chan bool)
c <- true //写入
<- c //读取
非缓冲通道
无论是发送操作还是接收操作,一开始执行就会被阻塞,直到配对的操作也开始执行才会继续传递。由此可见,非缓冲通道是在用同步的方式传递数据。也就是说,只有收发双方对接上了,数据才会被传递。数据是直接从发送方复制到接收方的,中间并不会用非缓冲通道做中转。
缓冲通道
缓冲通道可以理解为消息队列,在有容量的时候,发送和接收是不会互相依赖的。用异步的方式传递数据。
c := make(chan int, value)
当 value = 0
时,通道
是无缓冲阻塞读写的,等价于make(chan int)
;当value > 0
时,通道
有缓冲、是非阻塞的,直到写满 value
个元素才阻塞写入。
读取一个已经关闭的通道会立即返回通道类型的零值
,而写一个已经关闭的通道会抛异常。如果通道里的元素是整型的,读操作是不能通过返回值来确定通道是否关闭的。
通过for range
语法读取,当通道为空时,循环会阻塞;当通道关闭,循环会停止。通过循环停止,我们可以认为通道已经关闭。
func main() {
var c = make(chan int, 3)
//子协程写
go func() {
c <- 1
close(c)
}()
//直接读取通道,存在不知道子协程是否已关闭的情况
//fmt.Println(<-c)
//fmt.Println(<-c)
//主协程读取:使用for...range安全的读取
for value := range c {
fmt.Println(value)
}
}