通俗解释
想象一下,goroutines是Go语言里的小精灵,每个小精灵都有自己的任务去做。Go语言创造了一个非常聪明的系统来管理这些小精灵,让它们可以和谐共处,高效完成任务。这个系统的核心部分,就像是一个非常灵活的调度官,我们可以用以下几个点来简单描述它的工作原理:
1. 轻量级小精灵(Goroutines)
- 每个goroutine都是一个轻量级的小精灵,它占用的资源非常少。这意味着你可以同时拥有成千上万个小精灵,而不会给你的系统带来太大负担。
2. 聪明的调度官(调度器)
- Go语言有一个内置的“调度官”来管理这些小精灵。调度官确保每个小精灵都有机会执行任务,并且尽量让每个CPU的核心都忙碌起来,没有时间浪费。
3. 工作队列
- 每个CPU核心都有自己的“工作队列”,里面排着等待执行任务的小精灵。一旦某个核心完成了当前任务,它就会从队列里取出下一个小精灵的任务来执行。
4. 工作窃取
- 如果某个CPU核心的队列空了,它会偷偷地去其他核心的队列里“窃取”一些任务过来,这样就保证了自己不会闲着,同时也帮助了忙不过来的兄弟。
5. 动态伸缩的背包(动态栈)
- 每个小精灵都有自己的背包(栈),背包里装着它们需要的工具和材料(变量和函数调用的信息)。这个背包是会根据需要伸缩的,如果任务多,背包就会变大;任务少了,背包就会缩小,这样就不会浪费空间。
6. 合作和沟通(Channels和同步)
- 小精灵们之间需要互相合作和沟通,Go语言给了它们一种特殊的工具——“通道”(Channels),通过它们可以安全地交换信息,就像递接力棒一样。此外,还有其他的同步工具,确保它们在需要的时候能够井然有序地协作。
通过这样一个高效、灵活的系统,Go语言让每个小精灵都能发挥出最大的能量,而且还能保证它们之间不会互相干扰,让整个程序运行得既快速又稳定。这就是goroutines的魔法!
goroutine核心原理
Go 为了提供更容易使用的并发方法,使用了 goroutine 和 channel。goroutine 来自协程的概念,让一组可复用的函数运行在一组线程之上,即使有协程阻塞,该线程的其他协程也可以被 runtime 调度,转移到其他可运行的线程上。最关键的是,程序员看不到这些底层的细节,这就降低了编程的难度,提供了更容易的并发。
Go 中,协程被称为 goroutine,它非常轻量,一个 goroutine 只占几 KB,并且这几 KB 就足够 goroutine 运行完,这就能在有限的内存空间内支持大量 goroutine,支持了更多的并发。虽然一个 goroutine 的栈只占几 KB,但实际是可伸缩的,如果需要更多内容,runtime 会自动为 goroutine 分配。
Goroutine 特点:
- 占用内存更小(几 kb)
- 调度更灵活 (runtime 调度)
从技术角度理解goroutine的原理
从技术角度深入讲解Go语言中goroutines的原理及其实现,我们需要关注Go运行时(runtime)的设计和调度器的工作方式。Go运行时是goroutines能够高效运行的基础,它负责goroutines的创建、调度、同步以及网络I/O等。
Go运行时(Runtime)
Go运行时是一个与语言紧密集成的环境,它提供了垃圾回收、goroutine调度、通道和其他关键功能。它是用C和Go语言编写的,并在每个Go程序中运行,无需额外安装。
Goroutines的创建
- 栈分配:每个goroutine开始时只有很小的栈,通常为2KB。栈可以按需增长和收缩,这使得创建数十万个goroutine成为可能。
- 创建过程:当使用
go
关键字启动一个函数时,Go运行时会在堆上分配一个新的goroutine结构,包含栈(用于执行函数调用)和goroutine的状态。
调度器(Scheduler)
Go运行时包含一个内部调度器,用于管理goroutines的执行。这个调度器是M:N调度模型的一个实现,它将M个goroutines映射到N个操作系统线程上。
- M:N调度:调度器尝试在用户级别平衡负载,将活跃的goroutine分配给可用的线程,而不是依赖操作系统的线程调度。这样可以减少上下文切换的成本,并允许更高的并发。
- 工作窃取:调度器使用工作窃取策略来平衡多个处理器上的负载。当一个线程运行完当前的goroutine队列中的任务时,它可以从其他线程的队列中“窃取”任务来执行。
Goroutines的调度
- G、M、P的概念:调度器使用三种主要的抽象来管理goroutines的执行:
- G(Goroutine):代表一个goroutine的执行上下文。
- M(Machine):代表一个内核线程,可以执行G。
- P(Processor):代表逻辑处理器,其数量通常等于机器的CPU核数。P维护一个本地运行队列,G需要P的资源才能在M上执行。
- 调度循环:P会从其本地运行队列中取出G来执行。如果P的本地队列为空,它会尝试从其他P窃取G,或者从全局队列中获取G。
同步与通信
- Channels:goroutine间的主要通信机制。Channels提供了一种同步机制,因为它们在发送和接收数据时会阻塞,直到另一端准备好。
- 同步原语:Go运行时还提供了互斥锁(mutexes)、等待组(wait groups)等传统的同步机制,以方便在goroutines之间同步共享资源的访问。
总结
goroutines的轻量级、高效调度和简便的通信机制是Go并发编程的强大之处。它们通过Go运行时的支持,使得开发者可以轻松地创建和管理成千上万的并发任务,而无需担心传统并发编程中常见的复杂性和安全问题。通过M:N调度、工作窃取策略和动态栈管理,Go确保了其并发模型既高效又灵活,适用于构建高并发的应用程序。