ToB企服应用市场:ToB评测及商务社交产业平台
标题:
深入解析 Go 语言 GMP 模型:并发编程的焦点机制
[打印本页]
作者:
十念
时间:
2024-7-30 12:27
标题:
深入解析 Go 语言 GMP 模型:并发编程的焦点机制
前言
本章是Go并发编程的起始篇章,在未来几篇文章中我们会围绕Go并发编程进行理论和实战的学习,欢迎关注我哦!
本章主要以先容GMP模型为主,偏向于面试和八股,目标是让小伙伴们注重于知识本身,面向面试,面向八股,面向加薪。
Go语言自诞生以来,就以其简洁、高效的并发模型著称。而这其中的焦点正是GMP模型。理解GMP模型的演进进程,能资助我们更好地掌握Go的并发编程。而Goroutine作为Go中的焦点概念,极大地简化了并发编程的复杂度。本文将详细阐述Go语言GMP模型的演变过程,并深入解析其设计理念和优点,并详细先容Goroutine的根本概念、优势及其使用方法,并结合具体代码示例进行说明。
面试标题
在阅读本文前,先带着以下几个关于GMP模型的面试标题进行思考,以加深理解和掌握:
什么是GMP模型?请表明其根本概念。
回答要点:表明G、M、P的概念及其在调度模型中的角色。
如何理解GMP模型中线程的内核态和用户态?
回答要点:区分内核态线程和用户态线程,并说明它们在GMP模型中的作用。
Go语言中的Goroutine与线程的映射关系是怎样的?为什么选择这种映射方式?
回答要点:表明Goroutine与线程的多对多映射关系及其优点。
GMP模型如何解决线程调度中的锁竞争问题?
回答要点:先容全局队列和本地队列的使用,以及G的分配机制。
GMP模型中的Stealing机制是什么?它如何工作?
回答要点:形貌Stealing机制的原理及其在Goroutine调度中的应用。
什么是Hand off机制?在什么情况下会使用该机制?
回答要点:表明Hand off机制及其在阻塞和系统调用中的应用。
如何理解GMP模型中的抢占式调度?它解决了哪些问题?
回答要点:说明抢占式调度的原理及其在防止协程饿死中的作用。
什么是G0和M0?它们在GMP模型中饰演什么角色?
回答要点:形貌G0和M0的界说及其在Goroutine调度中的功能。
请详细说明GMP模型中的调度策略。
回答要点:逐步表明Goroutine的创建、唤醒、偷取、切换、自旋、系统调用和阻塞处置惩罚策略。
如安在实际项目中调优GMP调度模型?
回答要点:讨论如何通过调解GOMAXPROCS等参数来优化调度性能。
带着这些问题阅读本文,可以资助你更系统地掌握GMP模型的焦点概念和调度机制,提高面试中的应答能力。
单进程时代
根本概念
在单进程时代,一个进程就是一个运行中的程序。计算机系统在执行程序时,会重新到尾依次执行完一个程序,然后再执行下一个程序。在这种模型中,不必要复杂的调度机制,因为只有一个执行流程。
面对的两个问题
单一执行流程
:由于只能一个个执行程序,无法同时处置惩罚多个任务,这大大限定了CPU的使用率。
进程阻塞
:当一个进程遇到I/O操纵等阻塞情况时,CPU资源会被浪费,等待进程完成阻塞操纵后再继续执行,导致效率低下。
多进程/线程并发时代
根本概念
为了解决单进程时代的效率问题,引入了多进程和多线程并发模型。在这种模型中,当一个进程阻塞时,CPU可以切换到另一个准备好的进程继续执行。这样可以充分使用CPU资源,提高系统的并发处置惩罚能力。
两个问题
高开销
:进程拥有大量资源,进程的创建、切换和销毁都必要消耗大量的时间和资源。这导致CPU很大一部门时间都在处置惩罚进程调度,而不是实际的任务执行。
高内存占用
:在32位机器下,进程的虚拟内存占用为4GB,线程占用为4MB。大量的线程和进程会导致高内存消耗,限定了系统的扩展性。
协程的引入
为了解决多进程和多线程带来的高开销和高内存占用问题,引入了协程(Coroutine)。协程是一种比线程更轻量级的执行单元。协程在用户态进行调度,避免了频繁的上下文切换带来的开销。Go语言的GMP模型正是基于协程的设计。
协程的根本概念
在深入了解Goroutine之前,先来了解一下协程(Coroutine)的根本概念。
内核态和用户态
内核态线程
:由操纵系统管理和调度,CPU只负责处置惩罚内核态线程。
用户态线程
:由用户程序管理,需绑定到内核态线程上执行,协程即为用户态线程的一种。
内核态和用户态线程关系图
Kernel Space(内核空间)
:上半部门的灰色区域,表示操纵系统管理的内核空间。
User Space(用户空间)
:下半部门的白色区域,表示用户程序运行的空间。
Kernel Thread 1 和 Kernel Thread 2(内核线程)
:由操纵系统管理的内核线程,CPU直接处置惩罚这些线程。
User Thread 1、User Thread 2 和 User Thread 3(用户线程)
:由用户程序管理的用户线程(协程),需绑定到内核线程上执行。
执行流程
用户态线程:
用户程序创建多个用户线程(如协程),如图中的“User Thread 1”、“User Thread 2”和“User Thread 3”。
内核态线程:
用户线程需绑定到内核态线程上执行,如图中的“Kernel Thread 1”和“Kernel Thread 2”。
CPU处置惩罚:
CPU只处置惩罚内核态线程,通过绑定关系,用户态线程的执行也依赖于内核态线程的调度。
图中的赤色箭头表示CPU正在处置惩罚内核线程,从而间接处置惩罚绑定的用户线程。
线程和协程的映射关系
单线程绑定所有协程
:
问题1
:无法使用多核CPU的能力。
问题2
:假如某个协程阻塞,整个线程和进程都将阻塞,导致其他协程无法执行,丧失并发能力。
一对一映射
:
将每个协程绑定到一个线程上,退回到多进程/线程的模式,协程的创建、切换、销毁均需CPU完成,效率低下。
多对多映射
:
允许多个协程绑定到多个线程上,形成M:N的关系。这样可以充分使用多核CPU,并通过协程调度器高效管理协程的执行。
Goroutine
Goroutine是Go语言中的协程,实现了轻量级并发。与传统的线程相比,Goroutine具有以下显著特点:
轻量级
Goroutine非常轻量,初始化时仅占用几KB的栈内存,并且栈内存可以根据必要动态伸缩。这使得我们可以在Go程序中创建成千上万个Goroutine,而不会消耗过多的系统资源。
高效调度
Goroutine的调度由Go语言的运行时(runtime)负责,而不是操纵系统。Go运行时在用户态进行调度,避免了频繁的上下文切换带来的开销,使得调度更加高效。
Goroutine的使用示例
下面是一个简朴的示例,展示了如安在Go语言中使用Goroutine进行并发编程。
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("Hello")
go say("World")
time.Sleep(1 * time.Second)
fmt.Println("Done")
}
复制代码
在这个示例中,两个Goroutine同时执行,分别打印"Hello"和"World"。通过使用go关键字,我们可以轻松地启动一个新的Goroutine。
必要注意的事项
主Goroutine的竣事
:在Go程序中,main函数本身也是一个Goroutine,称为主Goroutine。当主Goroutine竣事时,所有其他Goroutine也会随之终止。因此,必要确保主Goroutine等待所有子Goroutine执行完毕。
同步和共享数据
:固然Goroutine之间共享内存空间,但必要通过同步机制(如通道和锁)来避免竞争条件。Go语言保举使用通道(channel)进行Goroutine之间的通讯,以保证数据的安全性和同步性。
示例:使用通道进行同步
下面的示例展示了如何使用通道来同步多个Goroutine的执行。
[code]package mainimport ( "fmt" "sync")func worker(id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Worker %d starting\n", id) // 模仿工作 fmt.Printf("Worker %d done\n", id)}func main() { var wg sync.WaitGroup for i := 1; i
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4