Golang:线程 和 协程 的区别

国庆越快各位,距离上次发文快两个月了,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 的协程调度机制

如果不设置 runtime.GOMAXPROCS(1)
,那么程序将会根据操作系统的 CPU 核数而启动对应数量的 P,导致多个 M,即线程的启动。那么我们程序中的协程,就会被 分配到不同的线程
里面去了。为了演示,故设置数量 1,使得它们都被分配到了同一个线程里面,存于线程的协程队列里面,等待被执行或调度。

协程特点中的第 3 和 第 4 点。

  1. 执行效率高。
  2. 占用内存少。

因为 协程的调度切换不是线程切换
,而是由程序自身控制,因此, 没有线程切换的开销
,和多线程比,线程数量越多,协程的性能优势就越明显。调度发生在应用态而非内核态。
内存的花销,使用其所在的线程的内存,意味着线程的内存可以供多个协程使用。

其次协程的调度 不需要多线程的锁机制
,因为只有一个线程,也 不存在同时写变量冲突
,所以执行效率比多线程高很多。