当前位置: 首页 > article >正文

玩转goroutine:Golang中对goroutine的理解

文章目录

    • 前言
    • 什么是 goroutine?
    • goroutine 的基本工作原理
    • 启动和执行goroutine
    • goroutine与线程的区别
    • 调度与上下文切换
    • 同步机制
      • Channel同步机制
      • WaitGroup 等待执行
      • Mutex(互斥锁)
    • goroutine性能优化
    • 总结

前言

Go语言的 goroutine 是一种轻量级的线程管理机制,它是 Go 提供的并发编程的核心之一。与传统的线程相比,goroutine 的创建和销毁开销非常小,可以轻松地处理成千上万的并发任务。理解 goroutine 的机制对于高效编写 Go 并发代码非常重要。

什么是 goroutine?

在 Go 中,goroutine 是一个由 Go 运行时(runtime)管理的轻量级线程。你可以使用 go 关键字来启动一个新的 goroutine,它会在后台并发执行某个函数。不同于传统的线程,goroutine 的创建和销毁非常快速且开销小。

go myFunction()  // 启动一个 goroutine

goroutine 的基本工作原理

  • 轻量级:每个goroutine 都有自己的栈,但是初始栈非常小(通常是2KB),当需要更多的栈空间时,Go运行时会自动扩展栈的大小。
  • 多路复用:Go使用一种被称为M:N线程模型的机制。这里的M表示操作系统的线程数,N表示goroutine的数目。Go运行时会将多个goroutine映射到少量的操作系统线程上,从而实现轻量级的并发处理。
  • 调度器:Go运行时有一个调度器负责管理所有的goroutine,调度器会在不同的goroutine之间切换,并确保它们并发执行。

启动和执行goroutine

通过go关键字可以启动一个新的goroutine

func printNumbers() {
	for i := 1; i <= 5; i++ {
		fmt.Println(i)
	}
}
func main() {
	go printNumbers(). // 启动goroutine
	fmt.Println("Main function is done.")
}

在以上的例子中,printNumbers函数会在一个goroutine中执行,而main函数继续执行其他代码,输出的顺序上不确定的,因为goroutine的调度顺序是由Go运行时决定的。

goroutine与线程的区别

  • 内存开销goroutine的内存开销远小于操作系统进程,通常一个goroutine的栈空间是2KB,而一个操作系统线程的栈通常是几MB
  • 创建开销:创建goroutine的开销要远低于创建操作系统线程,因为不需要与操作系统进行大量的上下文切换和资源分配。
  • 调度:Go运行时调度器负责管理goroutine的调度,多个goroutine共享较少的操作系统线程,这样可以大大提高并发度。

调度与上下文切换

Go的调度器会定期将goroutine切换到不同的CPU核心上执行。调度器会根据以下原则来决定何时切换goroutine:

  • 如果goroutine执行的任务需要较长时间,调度器可能会进行主动切换,允许其他goroutine执行。
  • goroutine执行阻塞操作时(例如等待I/O),Go允许时会将其挂起,并运行其他goroutine

同步机制

虽然goroutine是并发执行的,但在很多情况下我们需要协调它们的执行,避免并发冲突。Go提供了多种同步机制:

Channel同步机制

Go中的Channel是一种非常常用的同步机制,用于在不同的goroutine之间传递数据。通过channel可以将数据从一个goroutine发送到另一个goroutine并实现同步。

func sayHello(c chan string) {
	c <- "Hello from goroutine!"
}
func main(){
	ch := make(chan string) // 创建一个channel
	go sayHello(ch)  // 启动goroutine
	msg := <-ch  // 从ch接收数据
	fmt.Println(msg) // 输出从"Hello from goroutine!"
}

WaitGroup 等待执行

sync.WaitGroup用于等待一组goroutine执行完成,它通常用于在主程序中等待多个goroutine完成工作。

import "sync"

func doWork(wg *sync.WaitGroup){
	defer wg.Done() // 执行完成后调用Done(),通知WaitGroup
	fmt.Println("Working...")
}

func main() {
	var wg,sync.WaitGroup
	wg.Add(2) // 计数器增加2
	go doWork(&wg)
	go doWork(&wg)
	wg.Wait() // 等待所有goroutine执行完成
}

Mutex(互斥锁)

当多个goroutine共享某个资源时,可能会发生数据竞争。Go提供了sync.Mutex来保证在同一时刻只有一个goroutine 能访问共享资源。

import "sync"

var mu,sync.Mutex
var counter int

func increment() {
	mu.Lock()   // 上锁,防止其他goroutine 访问
	counter ++
	mu.Unlock()   // 解锁,允许其他goroutine 访问
}

func main() {
	for i := 0; i< 1000; i++ {
		go increment()
	}
	time.Sleep(time.Second)  // 等待goroutine执行完成
	fmt.Println(counter)
}

goroutine性能优化

  • 避免频繁创建销毁:尽量避免频繁创建和销毁大量的goroutine。可以通过连接池或工作池的方式来复用goroutine
  • 栈增长与收缩:Go会自动调整goroutine的栈大小,如果需要更大的栈,它会动态扩展栈道大小。理解栈道增长和收缩对优化内存管理非常重要。
  • 合理使用同步机制:尽量减少对锁(Mutex)等同步机制的依赖,避免性能瓶颈。可以考虑使用channel作为代替。

总结

  • goroutine是Go的核心特性,允许你高效地进行并发编程。
  • Go使用了轻量级的线程管理,每个goroutine占用的内存非常小,创建和销毁开销低。
  • Go的调度器将多个goroutine映射到较少的操作系统线程上,通过多路复用实现高效的并发。
  • 通过channelWaitGroupMutex等机制,可以确保goroutine之间的同步和协作。

http://www.kler.cn/a/533930.html

相关文章:

  • 导入了fastjson2的依赖,但却无法使用相关API的解决方案
  • [x86 ubuntu22.04]进入S4失败
  • Noise Conditional Score Network
  • DeepSeek R1技术报告关键解析(6/10):DeepSeek-R1 vs. OpenAI-o1-1217:性能对比分析
  • w193基于Spring Boot的秒杀系统设计与实现
  • C++ 学习:深入理解 Linux 系统中的冯诺依曼架构
  • 多用户同时RDP登入Win10
  • 大型三甲医院算力网络架构的深度剖析与关键技术探索
  • JAVA 二维列表的基础操作与异常
  • python实现多路视频,多窗口播放功能
  • LeetCode:647.回文子串
  • java进阶专栏的学习指南
  • HTML5 教程之标签(3)
  • 2025春招,深度思考MyBatis面试题
  • 修剪二叉搜索树(力扣669)
  • 2025最新软件测试面试大全
  • 实现一个 LRU 风格的缓存类
  • java进阶1——JVM
  • 通过C/C++编程语言实现“数据结构”课程中的链表
  • Leetcode—159. 至多包含两个不同字符的最长子串【中等】Plus
  • ip属地是手机号还是手机位置?一文理清
  • 车载以太网__传输层
  • SpringBoot+SpringDataJPA项目中使用EntityManager执行复杂SQL
  • RabbitMQ中的过期时间
  • OCT图像缺陷检测
  • SpringUI Web高端动态交互元件库