发布时间:
概念 #
- 线程:操作系统级别的调度单元,每个线程都有自己的栈空间、上下文环境等,通常占用较多的系统资源。创建和销毁线程的开销较大,线程的数量也受到操作系统的限制
- 协程:协程是 Go 语言运行时(runtime)管理的调度单元,比线程更轻量级。每个协程的栈空间初始大小较小(通常是 2KB),并且可以根据需要动态扩展。创建和销毁协程的开销非常小,因此可以轻松创建数百万个协程
官方内容 #
goroutines
是使并发易于使用的一部分。这个想法已经存在了一段时间,就是将独立执行的函数(协程)多路复用到一组线程上。当协程阻塞时(例如通过调用阻塞系统调用),运行时会自动将同一操作系统线程上的其他协程移动到不同的可运行线程,这样它们就不会被阻塞。程序员看不到这些,这就是重点。结果,我们称之为 goroutines
,可以非常便宜:除了堆栈内存(只有几千字节)之外,它们几乎没有开销。
为了使堆栈变小,Go 的运行时使用可调整大小的有界堆栈。新创建的 goroutine
被分配几千字节,这几乎总是足够的。当它不够时,运行时会自动增加(和缩小)用于存储堆栈的内存,从而允许许多 goroutine
驻留在适量的内存中。CPU 开销平均每个函数调用大约三个廉价指令。在同一个地址空间中创建数十万个 goroutine
是可行的。如果 goroutine
只是线程,系统资源耗尽的数量就会少得多。
结论:一个进程有多个线程(板上钉钉),一个线程有多个协程(个人结论),所以它不是线程而是协程。
协程通信 #
c:= make(chan struct{})
创建一个结构体通道用于信号传递go func(){}()
启动一个协程c<- struct{}{}
发送一个空结构体的值<-c
阻塞主线程,此处作用只有一个阻塞线程。如果注释掉此处代码,输出 333、111,222 不会输出,因为此时主线程已结束
c:= make(chan struct{})
go func() {
fmt.Println(111)
time.Sleep(10 * time.Millisecond)
c<- struct{}{}
fmt.Println(222)
}()
<-c
fmt.Println(333)
输出
111
333
222
通信操作符号 <- #
简单理解,c 在 <-
前发送,c 在 <-
后接收,c 创建的通道名称
发送数据
c<- struct{}{}
接收数据
<-c