跳至正文

GO语言高效的本质

GO语言的高效性来自于其设计的多个方面:

    1. 并发性:GO语言天生支持并发,使用Goroutines实现轻量级的线程,可以轻松地实现并行计算和处理高并发的网络请求。

    2. 内存管理:GO语言拥有自动内存管理和垃圾回收机制,大大减少了程序员在内存管理方面的负担,避免了内存泄漏和悬挂指针等问题。

    3. 高效的编译器:GO语言的编译器可以将代码快速地编译成机器码,执行速度非常快。

    4. 简洁的语法:GO语言的语法简洁易懂,减少了代码的复杂度和出错的可能性。

    5. 高效的标准库:GO语言的标准库提供了丰富的功能,例如高效的网络库、并发库、加密库等,可以快速开发高效的应用程序。

因此,综合以上几个方面的设计,GO程序可以快速编译、高效运行,并且易于维护。

其中最重要,也最有特点的就是并发性以及内存管理,今天我们就来聊一下并发性。

说到并发性就离不开进程、线程这些老生常谈的概念,这里给大家补一下课。

进程:顾名思义,就是进行中的程序,一个exe或者apk的安装包并不是进程,因为它没有进行,当它运行起来,就成为了进程。在计算机中,进程是正在执行的程序的实例(比如你电脑上正在运行的微信、QQ都是一个进程)。

 每个进程都有自己的内存空间、文件描述符、网络连接等资源。进程之间相互独立,互相隔离,一个进程的错误不会影响其他进程的运行。因此,进程是计算机操作系统中最基本的执行单元,用于实现多任务和并发处理。

在现代操作系统中,每个进程都有一个唯一的标识符(PID),用于区分不同的进程。操作系统通过调度算法来分配处理器时间片,并控制进程之间的通信和资源竞争问题,从而实现多任务和并发处理。

线程:线程是计算机中一种基本的执行单元,是进程中的一个独立的执行序列,可以看作是轻量级的进程。一个进程中可以包含多个线程,这些线程共享进程的资源,包括内存空间、文件描述符、网络连接等等。

每个线程都有自己的堆栈、程序计数器和寄存器等运行时状态,线程之间可以并发地执行,以提高程序的执行效率。不同的线程之间可以通过共享内存等方式进行通信和数据交换,从而实现多任务和并发处理。

与进程不同,线程没有自己的地址空间和资源,它们共享进程的资源。因此,线程的创建和销毁比进程更快,占用的内存和系统资源也更少。同时,线程之间的切换也比进程更快,可以更有效地利用计算机的资源。

总之,线程是进程中的一个独立的执行序列,是计算机中最基本的并发执行单元。通过合理地利用线程,可以提高程序的执行效率,实现多任务和并发处理。

GO语言最大的特点之一就是拥有语言层面的线程-协程(是协程不是携程),它叫做Goroutine,它是GO语言的一种并发执行的机制,是一种轻量级的线程,可以在单个进程中并发执行多个任务,而无需开辟额外的线程或进程。

进程、线程都是操作系统层面的概念,而Goroutine是语言层面的,自由、灵活、无拘无束,如图

Goroutine是由GO语言运行时系统自动管理的,我们不需要操心,通过使用关键字”go”就可以启动一个Goroutine,例如:

概念介绍差不多了,接下来是更重要的调度模型。

GPM 是 Go 语言运行时系统的核心模型,也是实现 Goroutine 并发编程的基础。

GPM 模型是由三个重要的组件构成:G(协程)、P(处理器)、M(线程),先来张图:

好吧,来个正式点的(不好看的)

M(线程):M 也就是前文说的线程,是真正干活的家伙。它是Go 运行时系统中的实际执行单元,它代表着一个系统线程,可以执行 Goroutine。

Go 运行时系统会根据需要创建或销毁 M,并将 Goroutine 分配到 M 上执行。由于 M 是实际执行 Goroutine 的线程,因此它与操作系统线程之间的切换是昂贵的,Go 运行时系统会尽可能地减少M 的创建和销毁。

P(处理器):P 是 Go 运行时系统中的调度单元,它负责将 Goroutine 分配到 M 中执行,并负责 Goroutine 的调度和垃圾回收等工作。每个 P 都有一个本地 Goroutine 队列和一个本地调度器,以及一些与 Goroutine 调度相关的状态信息。

P的数量是由Go程序运行时自动决定的,默认情况下等于CPU的核数,通常情况下,每个CPU核会分配一个P,以便充分利用多核CPU的计算能力(若开启超线程则是CPU核数两倍)。

P是GPM模型中非常重要的一部分,它是GO语言并发机制的核心所在。通过将多个Goroutine绑定到多个P上,并利用调度器进行智能调度,GO语言能够实现高效的并发处理。

G:即Goroutine,它是 Go 语言中并发编程的基本单元,Goroutine 采用协作式调度方式,由 Go 运行时系统进行调度和管理,可以高效地支持大量的并发执行。

通过 GPM 模型,Go 语言可以高效地实现并发编程,支持大量的 Goroutine 并发执行,同时通过 P 的调度和 M 的管理,还可以实现高效的 Goroutine 调度和垃圾回收等功能,GPM模型的基本思想是将Goroutine的调度从操作系统的线程上抽象出来,使用M和P实现Goroutine的并发执行,从而提高系统的并发处理能力。

那么GPM具体是怎么调度的,有哪些场景?

1. 队列轮转:每个处理器P维护着一个G的队列,处理器P依次将协程G调度到M中执行。同时,每个P会周期性地查看全局队列中是否有G等待被运行并将其调度到M中执行,这是为了防止全局队列中的G被饿死。

2. 系统调用:如果 Goroutine 进行系统调用,导致 Goroutine 阻塞 M,有时调度程序能够将 Goroutine 从 M 中切换出去,并将新的 Goroutine 切换到同一个 M 上。但是,有时需要一个新的 M 来继续执行在 P 中排队的 Goroutine(分为同步系统调用和异步系统调用,先挖个坑,之后再详细地解释其工作原理)。

3. 协程窃取:当某个P没有需要调度的协程时,将从其他处理器中窃取(也就是偷)协程,这有助于保持调度效率。首先,我们最不希望看到的是小苦力M (当然它并不觉得苦,相反,它们乐此不疲)进入等待状态,因为一旦发生这种情况,操作系统就会将 M 从核心中切换出来,这意味着 P 无法完成任何工作,即使有一个处于可运行状态的 Goroutine,直到 M 在 Core 上切换回上下文。

工作窃取还有助于平衡所有 P 的 Goroutine,以便更好地分配工作并更有效地完成工作,因为G队列也有局部性,即它更依赖当前的P,所以会优先看看全局队列是否有待运行的,没有的话,才去窃取其他P的。

总的来说,GPM模型的调度器是GO语言高效性的关键所在,它能够有效地利用系统的资源,提高系统的并发处理能力。GPM模型的实现对于GO语言的高效性至关重要,它使得GO语言能够快速创建和销毁大量的Goroutine,并且在高并发和并行计算等方面表现出色。

*声明:本文于网络整理,如来源信息有误或侵犯权益,请联系我删除或授权事宜。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注