协程Goroutine和线程之间的区别和关系
在 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 运行时环境调度 |
上下文切换 | 开销较大 | 开销较小 | 开销较小 |
内存占用 | 相对较高 | 相对较低 | 相对较低 |
创建和销毁开销 | 较高 | 较低 | 较低 |
执行模型 | 并发/并行执行 | 协作式并发 | 并发/并行执行 |
资源共享 | 可共享进程资源 | 通常独立的栈和局部变量 | 可共享内存 |
应用场景 | 广泛的并发/并行处理 | 协作式多任务,事件驱动 | 简化并发编程,网络服务 |