为什么要设计Go协程
在我们开始介绍goroutine(后面称gr)之前, 我们需要了解为什么, Go需要自己设计一套自己的"routine", 为什么还需要自己实现调度.
我们知道, 线程(thread), 是操作系统调度的最小单位, 并且操作系统会有自己的一套逻辑来调度管理这些Thread. 这些资源以及管理模式全都是现成可用的. 事实上, C/C++, Java中的确也是这么做的, 他们的pthread也就是用于做这些事的. 那这现成的一套有什么不好呢?
Thread太重: Thread相比较于process已经非常轻量了, 但也不是我们想要的"那么轻量". thread作为一支完整的线程具备信号掩码,上下文环境以及各种控制信息等, 这其中已经出现了很多多余的Go并不需要的数据了. 此外一个thread默认占用栈空间1M, 这样的大小使得你无法大量去创建线程
Thread切换开销大: 要知道既然你创建的是thread, 那么在操作系统看来, 主函数还是一个简单的线程, 就没什么区别了, thread切换是要经过穿过用户态到达内核态的, 因此上下文切换开销很大
Thread间通信困难: thread间通信虽然有很多机制可选, 但实际使用起来复杂, 一旦涉及到shared memory, 各种锁的问题立刻就来了, 死锁的问题就变成家常便饭.
程序不好写: 创建一个线程如果还好, 那么回收一个线程就十分麻烦了, 需要去判断是不是detached还是需要去Join. 逻辑十分复杂. 此外, 考虑到我们无法大量创建线程, 那么我们就需要在有限的线程里做多路复用, epoll就是为了解决这个问题而出现的. 就算是这样, 程序也非常不好写, 不好维护, 也不好看
无法满足GC的需求: 最主要的问题还是无法满足GC, GC需要STW, 要求内存保持一种一致的状态, 在使用thread的情况下, 假设我们有10个线程, 我们现在需要等10个线程全部停下来. 但是假设我们是有10个gr/一个线程, 一个线程内只会有一个gr在运行. 因此我们只需要等这一个gr停下即可, 相比于等10个线程停下可以说是拉了一个总闸, 方便很多
在这里引用一段英文, 我认为总结非常到位
Threads have their own signal mask, can be assigned CPU affinity, can be put into cgroups and can be queried for which resources they use. All these controls add overhead for features that are simply not needed for how Go programs use goroutines and they quickly add up when you have 100,000 threads in your program
线程并不是不好, 设计理念也并没有出问题. 只是在Go语言中我们针对并发的需求, 并没有这么庞大且全面, 我们也许只是并发的去发一个request, 也许只是并行的计算个什么东西, 很多时候并不是一些很难的东西, 我们也不需要去内核态中切换, 也不需要进程掩码. 在Go语言中我们希望它写起来很简单, 不要复杂, 很小, 我能在我的机器上创建一大堆出来最好.
Last updated
Was this helpful?