跳到主要内容

协程Goroutine和线程之间的区别和关系

· 阅读需 4 分钟
素明诚
Full stack development

在 Windows 操作系统中,线程是内核级别的,由操作系统管理,每个线程对应一个执行路径;

协程是用户级别的,通常运行在一个线程之上,按照程序员通过代码定义的顺序执行;

Goroutine 是特定于 Go 语言的协程,由 Go 运行时管理

从 Windows 内核角度来说,它们就是普通的用户空间线程上的任务。

线程(Thread)

在 Windows 中,线程是能够在系统中执行的最小单位,是进程的一部分,共享进程的资源,但有自己的独立执行序列(或控制流)。

线程是由 Windows 内核管理和调度的,能够被操作系统调度器分配到不同的处理器核心上运行。

线程有自己的堆栈空间,用于存储局部变量和函数调用,但它们共享其所属进程的内存和资源。

线程之间的切换涉及到保存和加载寄存器,更新虚拟内存映射等,这在系统级别上是有开销的。

协程(Coroutine)

协程是用户级别的,意味着它们完全在用户空间中运行,Windows 内核并不直接管理或调度协程。

协程在单个线程中“多任务”,可以想象成在一个线程中有多个任务轮流执行,但在任何时刻都只有一个在工作。

协程的调度是程序员控制的,通常是通过库实现的。

协程之间的上下文切换非常轻量,因为不需要内核介入,只涉及到变量的状态变化。

Goroutine(Go 中的协程)

Goroutine 是 Go 语言特有的,是对协程概念的一种实现,但具有一些独特的属性,尤其是它们之间的通信方式。

在 Windows 上运行的 Go 程序会启动一些线程,Go 运行时(runtime)会在这些线程上调度许多 Goroutine。

尽管在技术上,每个 Goroutine 都是一个独立的执行路径,但它们实际上是在这些线程之上运行的。

如果你还没明白,可以看下面这个例子

线程(Thread):想象线程就像是公司的员工。每个员工都有自己的任务和责任,但他们共享公司的资源(例如办公室、打印机等)。员工(线程)的上下班(开始和结束线程)以及工作调度(线程切换)由公司管理层(操作系统)控制,想想就挺残忍的。

如果公司要新增一个员工或者安排员工之间的工作,这需要管理层的直接参与,也会涉及到较多的人力和物力资源(也就是说,线程的创建和上下文切换成本相对较高)。

协程(Coroutine):现在想象协程就像是在家工作的自由职业者。他们使用自己的电脑和办公设备(拥有自己的堆栈和局部变量),并且自己决定什么时候工作、什么时候休息(编程者控制)。

他们可以随时暂停工作去喝杯咖啡或是散步(yield 或等待),然后再回来继续工作。所有这些活动的安排都不需要外部管理层的参与(用户级的调度),并且几乎不需要额外的资源(低成本的任务切换)。

Goroutine(Go 中的协程):Goroutine 就像是使用特殊工作方法的自由职业者团队。他们不仅可以自己安排工作时间(用户态调度),还使用一种特殊的通信方式 —— 他们不会直接交谈(共享内存),而是通过写信(传递消息)来沟通(Go 语言的通道机制)。这种工作方式使他们的合作更加高效和有序(并发编程更容易实现和管理)。

特性/机制线程 (Thread)协程 (Coroutine)Goroutine (Go 语言)
调度操作系统调度用户程序调度Go 运行时环境调度
上下文切换开销较大开销较小开销较小
内存占用相对较高相对较低相对较低
创建和销毁开销较高较低较低
执行模型并发/并行执行协作式并发并发/并行执行
资源共享可共享进程资源通常独立的栈和局部变量可共享内存
应用场景广泛的并发/并行处理协作式多任务,事件驱动简化并发编程,网络服务