专栏首页林冠宏的技术文章Golang:线程 和 协程 的区别

Golang:线程 和 协程 的区别

目录

  • 前言
  • 协程
  • 协程的特点
    • 第 1第 2
    • 特点中的第 3 和 第 4 点
  • 和线程的整体对比

前言

国庆越快各位,距离上次发文快两个月了,19年也快结束了。现在的总结更多是放在了草稿 而没有发出,这次详细分享下在 Go 中,线程和协程的区别及其关系

协程

协程,英文名Coroutine。但在 Go 语言中,协程的英文名是:gorutine。它常常被用于进行多任务,即并发作业。没错,就是多线程作业的那个作业。

虽然在 Go 中,我们不用直接编写线程之类的代码来进行并发,但是 Go 的协程却依赖于线程来进行。

下面我们来看看它们的区别。

线程的基础介绍,这里请自行网上搜索文章,因为关于线程的优秀介绍文章已经很多。

协程的特点

这里先直接列出线程的特点,然后从例子中进行解析。

  1. 多个协程可由一个或多个线程管理,协程的调度发生在其所在的线程中。
  2. 可以被调度,调度策略由应用层代码定义,即可被高度自定义实现。
  3. 执行效率高。
  4. 占用内存少。

上面第 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. 占用内存少。

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

内存的花销,使用其所在的线程的内存,意味着线程的内存可以供多个协程使用。

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

和线程的整体对比

比较的点

线程

协程

数据存储

内核态的内存空间

一般是线程提供的用户态内存空间

切换操作

操作最终在内核层完成,应用层需要调用内核层提供的 syscall 底层函数

应用层使用代码进行简单的现场保存和恢复即可

任务调度

由内核实现,抢占方式,依赖各种锁

由用户态的实现的具体调度器进行。例如 go 协程的调度器

语音支持程度

绝大部分编程语言

部分语言:Lua,Go,Python ...

实现规范

按照现代操作系统规范实现

无统一规范。在应用层由开发者实现,高度自定义,比如只支持单线程的线程。不同的调度策略,等等

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 浅析 <路印协议--Loopring> 及整体分析 Relay 源码

    林冠宏-指尖下的幽灵
  • 通俗易懂,各常用线程池的执行 流程图

    作者:林冠宏 / 指尖下的幽灵 腾讯云+社区:https://cloud.tencent.com/developer/user/1148436/activ...

    林冠宏-指尖下的幽灵
  • 通俗易懂,各常用线程池的执行 流程图

    corePoolSize,maximumPoolSize,workQueue之间关系。

    林冠宏-指尖下的幽灵
  • 爱奇艺网络协程编写高并发应用实践

    本⽂以爱奇艺开源的⽹络协程库(https://github.com/iqiyi/libfiber )为例,讲解⽹络协程的设计原理、编程实践、性能优化等⽅⾯内容。

    范蠡
  • 爱奇艺网络协程编写高并发应用实践

    本⽂以爱奇艺开源的⽹络协程库(https://www.jintianxuesha.com)为例,讲解⽹络协程的设计原理、编程实践、性能优化等⽅⾯内容。

    不会飞的小鸟
  • 小程序架构

    小程序上线以来,一向被称为“便携版”的APP,关于两者之间的区别,无外乎小程序相对轻便、开发成本低,下面让我们来看一下小程序的“庐山真面目”!

    只喝牛奶的杀手
  • 【Python 第38课】 模块

    如果说我比别人看得更远些,那是因为我站在了巨人的肩上。 -- 牛顿 python自带了功能丰富的标准库,另外还有数量庞大的各种第三方库。使用这些“巨人的”代码...

    Crossin先生
  • CPU核心数,线程数,时间片轮转机制解读

    当线程处于IO操作时,线程是阻塞的,线程由运行状态切换到等待状态。此时CPU会做上下文切换,以便处理其他程序;当IO操作完成后,CPU会收到一个来自硬盘的中断信...

    Anymarvel
  • Java并发学习之ThreadLocal使用及原理介绍

    ThreadLocal使用及原理介绍 线程本地变量,每个线程保存变量的副本,对副本的改动,对其他的线程而言是透明的(即隔离的) 1. 使用姿势一览 使用方式也...

    一灰灰blog
  • java学习与应用(3.3)--异常、多线程、lambda简

    异常是一个类,顶层父类为Throwable,分Error(错误,必须修正代码)和Exception(编译期异常)。Exception下有RunTimeExcep...

    嘘、小点声

扫码关注云+社区

领取腾讯云代金券