国庆越快各位, 距离上次发文快两个月了, 19 年也快结束了. 现在的总结更多是放在了 草稿 而没有发出, 这次详细分享下在 Go 中, 线程和协程的区别及其关系 .
协程
协程, 英文名 Coroutine . 但在 Go 语言中, 协程的英文名是: gorutine . 它常常被用于进行 多任务 , 即 并发作业 . 没错, 就是 多线程 作业的那个作业.
虽然在 Go 中, 我们不用直接编写线程之类的代码来进行并发, 但是 Go 的协程却 依赖于线程 来进行.
下面我们来看看它们的区别.
线程的基础介绍, 这里请自行网上搜索文章, 因为关于线程的优秀介绍文章已经很多.
协程的特点
这里先直接列出线程的特点, 然后从例子中进行解析.
协程的调度
上面 第 1 和 第 2 点
我们来看一个例子:
- func TestGorutine(t *testing.T) {
- runtime.GOMAXPROCS(1) // 指定最大 P 为 1, 从而管理协程最多的线程为 1 个
- wg := sync.WaitGroup{} // 控制等待所有协程都执行完再退出程序
- wg.Add(2)
- // 运行一个协程
- go func() {
- fmt.Println(1)
- fmt.Println(2)
- fmt.Println(3)
- wg.Done()
- }()
- // 运行第二个协程
- go func() {
- fmt.Println(65)
- fmt.Println(66)
- // 设置个睡眠, 让该协程执行超时而被挂起, 引起超时调度
- time.Sleep(time.Second)
- fmt.Println(67)
- wg.Done()
- }()
- wg.Wait()
- }
上面的代码片段跑了两个协程, 运行后, 观察输出的 顺序是交错 的. 可能是:
- 65
- 66
- 1
- 2
- 3
- 67
意味着在执行协程 A 的过程中, 可以 随时中断 , 去执协程行 B, 协程 B 也可能在执行过程中中断再去执行协程 A.
看起来协程 A 和 协程 B 的运行像是线程的切换, 但是请注意, 这里的 A 和 B 都运行在同一个线程里面. 它们的调度不是线程的切换, 而是 纯应用态的协程调度 .
关于上述代码中, 为什么要指定下面两行代码?
- runtime.GOMAXPROCS(1)
- time.Sleep(time.Second)
这需要您去看下 Go 的协程调度入门基础, 请看我之前的另外一篇调度分析文章:
Go 的协程调度机制 https://juejin.im/post/5b7678f451882533110e8948
如果不设置 runtime.GOMAXPROCS(1) , 那么程序将会根据操作系统的 CPU 核数而启动对应数量的 P, 导致多个 M, 即线程的启动. 那么我们程序中的协程, 就会被 分配到不同的线程 里面去了. 为了演示, 故设置数量 1, 使得它们都被分配到了同一个线程里面, 存于线程的协程队列里面, 等待被执行或调度.
协程特点中的第 3 和 第 4 点.
执行效率高.
占用内存少.
因为 协程的调度切换不是线程切换 , 而是由程序自身控制, 因此, 没有线程切换的开销 , 和多线程比, 线程数量越多, 协程的性能优势就越明显. 调度发生在应用态而非内核态.
内存的花销, 使用其所在的线程的内存, 意味着线程的内存可以供多个协程使用.
其次协程的调度 不需要多线程的锁机制 , 因为只有一个线程, 也 不存在同时写变量冲突 , 所以执行效率比多线程高很多.
来源: http://www.tuicool.com/articles/rIRfuyE