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 ,编写高效的并发程序。

目录
相关文章
|
7月前
|
Java 调度
Java并发编程:深入理解线程池的原理与实践
【4月更文挑战第6天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将从线程池的基本原理入手,逐步解析其工作过程,以及如何在实际开发中合理使用线程池以提高程序性能。同时,我们还将关注线程池的一些高级特性,如自定义线程工厂、拒绝策略等,以帮助读者更好地掌握线程池的使用技巧。
|
存储 缓存 Java
一文读懂线程池的实现原理
一文读懂线程池的实现原理
261 0
一文读懂线程池的实现原理
|
3月前
|
存储 缓存 Java
JAVA并发编程系列(11)线程池底层原理架构剖析
本文详细解析了Java线程池的核心参数及其意义,包括核心线程数量(corePoolSize)、最大线程数量(maximumPoolSize)、线程空闲时间(keepAliveTime)、任务存储队列(workQueue)、线程工厂(threadFactory)及拒绝策略(handler)。此外,还介绍了四种常见的线程池:可缓存线程池(newCachedThreadPool)、定时调度线程池(newScheduledThreadPool)、单线程池(newSingleThreadExecutor)及固定长度线程池(newFixedThreadPool)。
|
4月前
|
Java 程序员 容器
【多线程面试题二十四】、 说说你对JUC的了解
这篇文章介绍了Java并发包java.util.concurrent(简称JUC),它是JSR 166规范的实现,提供了并发编程所需的基础组件,包括原子更新类、锁与条件变量、线程池、阻塞队列、并发容器和同步器等多种工具。
|
7月前
|
监控 Java 调度
Java并发编程:线程池的原理与实践
【5月更文挑战第30天】 在现代软件开发中,尤其是Java应用中,并发编程是一个不可忽视的领域。线程池作为提升应用性能和资源利用率的关键技术之一,其正确使用和优化对系统稳定性和效率至关重要。本文将深入探讨线程池的核心原理、常见类型以及在实际开发中的使用案例,旨在帮助开发者更好地理解和运用线程池技术,构建高性能的Java应用程序。
|
存储 缓存 算法
Java多线程与并发-原理
Java多线程与并发-原理
64 0
|
存储 SQL 监控
Java线程池实现原理详解
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因
121 0
Java线程池实现原理详解
【多线程】线程池如何复用,怎么才能让面试官听懂我说的?
今天来说一下面试中常问到问题,我们知道线程池是帮助我们对线程资源的管理,只有我们合理的使用使用线程池,他才能做到事倍功半,但是你知道线程池是如何复用的吗?
|
存储 缓存 监控
Java并发编程系列之二线程基础
上篇文章对并发的理论基础进行了回顾,主要是为什么使用多线程、多线程会引发什么问题及引发的原因,和怎么使用Java中的多线程去解决这些问题。
Java并发编程系列之二线程基础