goroutine源码分析,直击并发底层实现

简介: goroutine源码分析,直击并发底层实现

概述

Go 语言中的 goroutine 是一种轻量级线程, goroutine 之间通过 channel 进行通信。

使用 goroutine 可以方便地编写并发程序。

本文将介绍 goroutine 的使用方法、调度原理、同步处理及实战案例等内容。

主要内容包括

goroutine 简介

goroutine 使用方法

goroutine 调度原理

goroutine 同步处理

避免 goroutine 泄漏和死锁

goroutine 使用技巧

goroutine 实战案例

goroutine 源码分析


 

1、goroutine 简介

goroutine 是 Go 语言提供的轻量级线程,具有以下特点

每个 goroutine 占用内存很小,只有 2KB 左右

调度成本小,可以轻松创建上万个 goroutine

goroutine 通过 channel 通信传递数据

由 Go 运行时(runtime)自动调度执行

goroutine 让并发编程变得更简单高效。


 

2、goroutine 使用方法

使用go语句即可启动一个 goroutine,语法如下


go 函数名(参数列表)

例如



// 开启处理data的goroutinego process(data)

启动时可传递参数,参数需为原值或引用类型,否则参数副本无法修改


func sum(s []int, ch chan int) {  sum := 0  for _, v := range s {    sum += v  }  ch <- sum // 返回结果}
go sum(s, resultCh)

匿名函数可直接启动 goroutine


go func() {  // 使用外部变量  ...}()


 

3、goroutine 调度原理

Go 运行时通过 GMP 模型调度 goroutine

G - goroutine

P - processor,对应 OS 线程

M - machine,关联线程栈等执行上下文

GMP 共同协作完成 goroutine 调度执行,runtime 会根据负载动态分配 P 与 G。

runtime 采用以下技术优化调度:

复用线程,减少创建开销

工作窃取算法动态平衡负载

GOMAXPROCS 参数限制 CPU 使用数


 

4、goroutine 同步处理

1. WaitGroup

WaitGroup 可以等待一组 goroutine 完成


var wg sync.WaitGroup
func process() {  wg.Add(1)   defer wg.Done()     // do work  }
wg.Add(3) // 设置期望goroutine数go process() // 启动多个goroutinewg.Wait() // 等待结束

2. Channel

Channel 可用于 goroutine 间同步


ch := make(chan int) 
go func() {  // 写入channel   ch <- 1 }()
// 从channel读取数据<-ch

可设置标志 channel 协调 goroutine


done := make(chan bool)
go func() {   done <- true}()  
<-done


 

5、避免 goroutine 泄漏和死锁

1. 泄漏

goroutine 可能发生泄漏


func main() {  ch := make(chan int)  go func() {    <-ch // 可能一直阻塞  }()}

解决方法是在 main goroutine 退出前,关闭 channel


close(ch) // 主动关闭channel

2. 死锁

死锁场景


ch := make(chan int)
func main() {  go func() {     ch <- 1 // 阻塞,等待接收  }()
  <-ch // 阻塞,等待发送  }

必须破坏死锁条件才能修复,如修改为单向 channel。


 

6、goroutine 使用技巧

优化 goroutine 使用的技巧

利用缓冲 channel 并发限速

避免过多阻塞与交互

合理设置 GOMAXPROCS

profiling 找出性能瓶颈

使用定时器防止泄漏

请注意不要启动过多阻塞的 goroutine 。


 

7、goroutine 实战案例

1.

func search(query string) {  var wg sync.WaitGroup  for _, site := range sites {    wg.Add(1)    go func(site string) {      defer wg.Done()      search(site, query)    }(site)  }  
  wg.Wait() // 等待结束}

2.

func sort(items []int) {  var wg sync.WaitGroup   ch := make(chan []int) // 分割后子序列 channel
  // 分割为多个子序列  for i := range items {    wg.Add(1)    go func(sub []int) {      defer wg.Done()        sort(sub) // 排序子序列      ch <- sub // 返回排序结果    }(items[i:j])   }
  // 合并排序结果  for range items {    sorted := <-ch    // 添加到最终结果  }
  wg.Wait()}


 

8、goroutine 源码分析

goroutine 的调度实现位于 runtime 下的 proc.go 文件中,主要涉及下面几个结构

g 对象,代表 goroutine ,定义在 proc.go


type g struct {  // goroutine状态   status int  // goroutine运行栈信息  stack stack    // 入口函数等信息  entry stack.entry}

m 对象,代表操作系统线程,在 proc.go


type m struct {  // 绑定的操作系统线程id  tid int   // 调度相关信息  sched sched  //  caches缓存  caches cache   // 关联的p  p p}

p 对象,代表逻辑处理器,在 proc.go


type p struct {  id int  status uint32  // 本地可运行goroutine队列  runq gQueue }

runtime 会使用 g 结构体对象表示 goroutine, g 对象保存 goroutine 状态、调度信息等;


m 代表机器结构,维护与操作系统线程相关资源;

p 代表处理器逻辑单元,绑定特定 m,可运行 g;

runtime 会维护全局 g 队列及 p 的本地可运行 g 队列;

当创建 goroutine 时,会保存 goroutine 入口等信息到 g 对象;

findRunnable 函数会从队列中弹出待执行 g,放入 p 的本地队列;

retake 会尝试从其他 p 的队列窃取 g 来执行。

runtime 通过这些手段管理 goroutine 的调度和执行,实现高效的并发。


 

总结

goroutine 是 Go 语言轻量级线程,可以大规模并发。了解 goroutine 机制,正确使用是 Go 并发编程的关键。

本文从多个维度详细介绍了 goroutine 的工作原理、使用技巧等内容,可以帮助读者深入地理解和运用 goroutine ,编写高效的并发程序。

目录
相关文章
|
6月前
|
Java 调度
【多线程面试题十四】、说一说synchronized的底层实现原理
这篇文章解释了Java中的`synchronized`关键字的底层实现原理,包括它在代码块和方法同步中的实现方式,以及通过`monitorenter`和`monitorexit`指令以及`ACC_SYNCHRONIZED`访问标志来控制线程同步和锁的获取与释放。
|
6月前
|
安全 Java
【多线程面试题 六】、 如何实现线程同步?
实现线程同步的方法包括同步方法、同步代码块、使用ReentrantLock、volatile关键字以及原子变量类,以确保线程安全和数据一致性。
|
9月前
|
存储 算法 Java
12张图一次性搞懂高性能并发容器ConcurrentLinkedQueue
12张图一次性搞懂高性能并发容器ConcurrentLinkedQueue
|
9月前
|
安全 Java
Java并发编程—并发流程控制与AQS原理及相关源码解析
Java并发编程—并发流程控制与AQS原理及相关源码解析
93 0
|
存储 缓存 算法
Java多线程与并发-原理
Java多线程与并发-原理
70 0
|
存储 SQL 监控
Java线程池实现原理详解
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因
136 0
Java线程池实现原理详解
|
安全 Java
Java多线程——生命周期、并发、临界资源问题
是进程执行的最小单元。这样说是不是很抽象?比方拿我们的浏览器来说,一个浏览器可以同时下载几幅图片,可以边听歌的同时边聊天、边播放视频的同时还可以打印文件,这每一个行为我们都可看作是一个不同的线程,不同的线程可以同时运行。
阿里巴巴面试题- - -多线程&并发篇(三十七)
阿里巴巴面试题- - -多线程&并发篇(三十七)
|
Java 调度 数据库
面试官:谈一谈java中基于AQS的并发锁原理
面试官:谈一谈java中基于AQS的并发锁原理
118 0
面试官:谈一谈java中基于AQS的并发锁原理
|
存储 安全 容器
【多线程】阻塞线程| 一图看懂ArrayBlockingQueue源码
是一个数组实现的环形队列,经常会使用并发容器用于存储多线程间的共享数据,这样不仅可以保证线程安全,还可以简化各个线程操作

相关实验场景

更多