为什么要用协程
我们经常会用到多线程去处理海量的请求,然而在Go中,是用多协程代替多线程去实现并发的。那协程和线程的区别是什么?Go中为什么要用协程呢?
协程和线程的区别
-
资源方面
linux及mac上默认的线程栈大小一般为8MB,而协程栈一般为2KB,节省内存。
-
调度方面
线程由操作系统调度,占用cpu时间,开销较大;协程由线程调度,开销小。
由于操作系统眼里只有一个线程,但这个线程中背地里操控着大量的协程。对于并发而言,更加轻便敏捷,因此Go用协程来解决并发。
协程的本质
且看下src库是如何实现协程的。
g结构体
Go将协程信息记录在src\runtime\runtime2.go下的g结构体,该结构体字段较多,以下只选择部分重要字段说明。
type g struct {
stack stack // 记录了协程的高地址和低地址
sched gobuf // 记录程序运行时的信息
atomicstatus uint32 // 协程的状态
goid int64 // 协程的id号
...
}
对每个字段拆分讲解:
stack,记录了协程的高地址和低地址。
type stack struct {
lo uintptr // 低地址
hi uintptr // 高地址
}
sched,是一个gobuf结构体,部分结构如下:
type gobuf struct {
sp uintptr // stackPointer 栈地址
pc uintptr // programCounter 协程计数器,记录程序运行的当前行数
...
}
atomicstatus, 记录协程的状态。
goid,协程的id号。
程序演示
以上程序写了三个简单函数,并在主函数中启用协程do1()。同时对13行的do3()进行断点,然后启动调试。
程序运行后会卡在13行do3()位置,这时相对应的g结构体如下:
stack的高低地址分别指向运行栈中地址的起始和末尾。
sched则记录了do2()这个正在运行函数的位置(断点卡住还没有运行do3函数),以及计数器记录了运行代码的13行位置。
线程
协程我们知道了是g结构体,而协程是基于某线程上运行的,那这个线程的信息Go记录在哪呢?
答案是在src\runtime\runtime2.go下的m结构体上,m的结构如下,重点是记录了g0协程和当前运行的协程。
type m struct {
g0 *g // 线程运行的第一个go协程
curg *g // 当前正在运行的协程
mOS // 针对不同的操作系统储存不同的线程信息
...
}
总结
以上只是简单介绍下线程和协程长什么样子,下一篇要深入细节研究下协程是如何在线程上运行的。