Go语言并发处理的使用详解

 更新时间:2026年03月05日 10:48:02   作者:moxiaoran5753  
Go语言通过内置的goroutine和channel机制,实现了高效的并发编程,本文就来介绍一下Go语言并发处理的使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

并发是 Go 语言最核心、最亮眼的特性,Go 从语言层面原生支持并发,不像 Java/Python 等语言需要依赖线程库、协程框架,它的并发设计简洁、轻量、高效,也是 Go 能在后端开发 / 云原生领域脱颖而出的核心原因。

一、Go 并发的核心思想:不要通过共享内存来通信,要通过通信来共享内存

这是 Go 语言之父 Rob Pike 提出的并发黄金法则,也是 Go 并发设计的底层逻辑:

  • 传统语言(Java/C++)并发:多线程抢占共享内存,通过 锁(synchronized/lock) 保证数据安全,容易出现死锁、竞态、锁争用等问题;
  • Go 语言并发:goroutine 之间不推荐共享内存,而是通过专门的 channel 管道传递数据,把数据所有权从一个 goroutine 转移到另一个,天然避免数据竞争,代码更安全、简洁。

二、Go 并发的两大基石

Go 的并发能力完全依赖两个核心组件实现,二者相辅相成,缺一不可:goroutine(协程) + channel(通道)

1. Goroutine (轻量级协程) - Go 并发的执行体

(1)什么是 Goroutine

goroutine 是 Go 语言内置的、轻量级的执行单元,也叫「用户级线程 / 协程」,它由 Go 运行时(runtime) 调度,而不是操作系统内核调度。

(2)Goroutine 核心特点

  • 极致轻量:一个 goroutine 的初始栈空间仅 2KB,并且栈空间可以动态扩容 / 缩容(最大几 MB),完全由 runtime 自动管理;
  • 资源占用极少:一台普通服务器上,轻松创建 10 万、甚至百万级别的 goroutine,不会有内存压力;而操作系统线程(thread)的栈空间默认是 1MB,创建几千个线程就会内存溢出;
  • 调度效率极高:Go 的 M/P/G 调度模型,能把 goroutine 高效映射到操作系统线程,调度切换的开销只有线程的几百分之一
  • goroutine 是并发执行:多个 goroutine 在同一个 Go 程序中,宏观上 “同时运行”,微观上由 runtime 做时间片轮转调度。

(3)如何创建 Goroutine

语法超级简单:在函数调用前加一个 go 关键字 即可,这个函数就会开启一个新的 goroutine 执行。

package main

import "fmt"

// 定义一个普通函数
func printNum(name string) {
	for i := 1; i <= 3; i++ {
		fmt.Printf("协程[%s]:输出数字 %d\n", name, i)
	}
}

func main() {
	// 1. 开启第一个goroutine执行printNum
	go printNum("goroutine-1")
	// 2. 开启第二个goroutine执行printNum
	go printNum("goroutine-2")
	
	// 注意:主goroutine执行完会直接退出,导致子goroutine来不及执行
	// 这里简单休眠1秒,等子goroutine执行完毕(仅演示,生产不用这个方式)
	fmt.Println("主协程开始等待...")
	import "time" // 实际代码要把import写在顶部
	time.Sleep(time.Second)
	fmt.Println("主协程执行结束")
}

执行结果(顺序不固定,因为 goroutine 并发调度):

主协程开始等待...

协程[goroutine-1]:输出数字 1

协程[goroutine-2]:输出数字 1

协程[goroutine-1]:输出数字 2

协程[goroutine-2]:输出数字 2

协程[goroutine-1]:输出数字 3

协程[goroutine-2]:输出数字 3

主协程执行结束

(4)补充:主 Goroutine

  • 每个 Go 程序启动后,会自动创建一个主 goroutine,执行 main() 函数;
  • 所有子 goroutine 由主 goroutine(或其他子 goroutine)创建;
  • 主 goroutine 退出,整个程序立即结束,所有子 goroutine 都会被强制终止(不管是否执行完)。

2. Channel (通道) - Go 并发的通信方式

(1)什么是 Channel

channel 是 Go 语言提供的原生同步通信机制,可以理解为「goroutine 之间的管道」。它的核心作用:让多个 goroutine 之间安全的传递数据、实现同步 / 协作,完全契合 Go 的并发思想:通过通信来共享内存

(2)Channel 核心特点

  1. 通道是类型安全的:声明通道时必须指定传递的数据类型,只能传递对应类型的数据;
  2. 通道是阻塞特性的(核心):
    • 向一个通道发送数据 ch <- data 时,如果通道满了,发送方 goroutine 会被阻塞,直到有 goroutine 从通道取走数据;
    • 从一个通道接收数据 data := <-ch 时,如果通道空了,接收方 goroutine 会被阻塞,直到有 goroutine 向通道发送数据;
    • 这种阻塞是 runtime 实现的,无额外开销,是 goroutine 协作的核心原理;
  3. 通道可以手动关闭,关闭后无法再发送数据,只能接收剩余数据;
  4. 通道天然解决「数据竞争」:goroutine 之间不用共享变量,而是通过通道传递数据,数据在 goroutine 之间 “流转” 而非 “共享”。

(3)如何创建 Channel

语法:通道变量 := make(chan 数据类型, [缓冲区大小])

Channel 分为两种:无缓冲通道有缓冲通道,通过「缓冲区大小」区分,默认是 0(无缓冲)。

① 无缓冲通道 (同步通道)

缓冲区大小为 0,声明时可以省略第二个参数:make(chan int)

  • 特性:发送和接收必须同时就绪,否则会阻塞;
  • 本质:实现 goroutine 之间的严格同步,发送方发数据的瞬间,必须有接收方在取数据,相当于 “一手交钱、一手交货”;
  • 适用场景:goroutine 之间的强同步协作。
package main

import "fmt"

func sendData(ch chan int) {
	fmt.Println("准备发送数据:100")
	ch <- 100 // 发送数据,无缓冲通道会阻塞,直到有goroutine接收
	fmt.Println("数据发送成功")
}

func main() {
	// 创建无缓冲int类型通道
	ch := make(chan int)
	// 开启goroutine发送数据
	go sendData(ch)
	
	// 主goroutine接收数据,此时sendData的阻塞才会解除
	data := <-ch
	fmt.Printf("主协程接收数据:%d\n", data)
}
② 有缓冲通道 (异步通道)

缓冲区大小为 >0 的整数,比如 make(chan int, 3) 表示能存放 3 个 int 数据的通道

  • 特性:发送数据时,只要缓冲区没满,发送方不会阻塞;只有缓冲区满了,发送方才会阻塞;接收数据时,只有缓冲区空了,接收方才会阻塞;
  • 本质:实现 goroutine 之间的异步通信,可以暂存数据,不用严格同步;
  • 适用场景:需要缓存数据、削峰填谷的并发场景。
package main

import "fmt"

func main() {
	// 创建有缓冲通道,缓冲区大小为2
	ch := make(chan string, 2)
	// 发送数据,缓冲区未满,不会阻塞
	ch <- "hello"
	ch <- "goroutine"
	fmt.Println("两个数据发送成功,缓冲区已满")

	// 接收一个数据,缓冲区剩余1个
	fmt.Println("接收数据:", <-ch)
	// 再发送一个数据,缓冲区未满,不会阻塞
	ch <- "channel"

	// 遍历接收所有数据
	for i := 0; i < 2; i++ {
		fmt.Println("接收数据:", <-ch)
	}
}

执行结果:

两个数据发送成功,

缓冲区已满

接收数据: hello

接收数据: goroutine

接收数据: channel

(4)Channel 的关闭与遍历

  • 关闭通道:close(ch)只能由发送方关闭,接收方关闭会 panic;
  • 判断通道是否关闭:接收数据时可以用 data, ok := <-chokfalse 表示通道已关闭且无数据;
  • 遍历通道:推荐用 for range ch,会自动遍历通道的所有数据,通道关闭后自动退出循环
package main

import "fmt"

func sendData(ch chan int) {
	for i := 1; i <= 3; i++ {
		ch <- i // 发送数据
	}
	close(ch) // 发送完成,关闭通道
	fmt.Println("通道已关闭")
}

func main() {
	ch := make(chan int, 2)
	go sendData(ch)

	// for range 遍历通道,自动处理关闭
	for data := range ch {
		fmt.Printf("接收数据:%d\n", data)
	}
	fmt.Println("遍历结束")
}

三、Go 并发的补充核心知识点

1. WaitGroup - 等待一组 Goroutine 执行完成

前面的例子中,我们用 time.Sleep 等待 goroutine 执行完毕,这是极不推荐的写法(睡眠时间无法精准控制)。

Go 的 sync 包提供了 WaitGroup,专门用来等待一组 goroutine 全部执行完成后,再继续执行主 goroutine,是生产环境中最常用的 goroutine 等待方式。

WaitGroup 核心方法

  1. wg.Add(n):设置需要等待的 goroutine 数量,n 是 goroutine 的个数;
  2. wg.Done():每个 goroutine 执行完成后调用,相当于 wg.Add(-1),表示完成一个;
  3. wg.Wait():主 goroutine 调用,阻塞直到所有 goroutine 都调用了 wg.Done()(计数归 0)。

示例

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup // 声明WaitGroup

func task(name string) {
	defer wg.Done() // 函数执行完自动调用Done,防止遗漏
	for i := 1; i <= 2; i++ {
		fmt.Printf("任务[%s]执行:%d\n", name, i)
	}
}

func main() {
	taskNum := 3
	wg.Add(taskNum) // 需要等待3个goroutine

	// 开启3个goroutine
	go task("goroutine-1")
	go task("goroutine-2")
	go task("goroutine-3")

	wg.Wait() // 阻塞等待所有任务完成
	fmt.Println("所有goroutine执行完毕,主协程退出")
}

2. Go 并发 vs 传统多线程并发(核心优势总结)

很多人会问:Java 也有线程、Python 也有协程,Go 的并发到底好在哪?这里做一个清晰的对比,也是 Go 的核心竞争力:

核心结论:Go 的并发不是 “锦上添花”,而是语言层面的降维打击,它让并发编程的门槛大幅降低,性能大幅提升,不用关心线程池、锁机制,就能写出高效、安全的并发代码。

四、Go 并发的进阶内容(拓展,按需学习)

这里补充两个进阶知识点,是 Go 并发的完整体系:

1. Mutex (互斥锁) - 共享内存的兜底方案

Go 的设计思想是「通信优先于共享」,但极少数场景下,goroutine 之间必须共享变量(比如全局计数器),此时可以用 sync.Mutex 互斥锁保证数据安全。

  • mutex.Lock():加锁,同一时间只有一个 goroutine 能获取锁;
  • mutex.Unlock():解锁,释放锁资源;
  • 注意:尽量少用锁,锁会带来竞态、死锁风险,能用 channel 解决的问题,坚决不用锁。

2. Select - 多路复用通道

select 是 Go 的关键字,专门用来同时监听多个 channel 的读写操作,实现多路并发控制,类似 Linux 的 IO 多路复用。

  • 特性:select 会随机选择一个就绪的 case 执行;如果所有 case 都阻塞,会阻塞到有 case 就绪;
  • 适用场景:同时处理多个通道、设置通道超时、非阻塞读写通道等。

总结

  1. Go 并发的核心思想:不要通过共享内存来通信,要通过通信来共享内存;
  2. Go 并发的两大基石:goroutine(轻量级协程,并发执行体) + channel(通道,协程间通信方式);
  3. Goroutine:轻量(2KB 栈)、高效、百万级创建,runtime 调度,主协程退出则程序结束;
  4. Channel:类型安全、阻塞特性,分无缓冲(同步)和有缓冲(异步),是 goroutine 的安全通信方式;
  5. 等待 goroutine 完成:用 sync.WaitGroup禁止用 time.Sleep
  6. Go 并发的核心优势:相比传统多线程,资源占用更少、调度更快、代码更简洁、数据更安全;
  7. 进阶兜底:必须共享变量时用 sync.Mutex,多路通道控制用 select

到此这篇关于Go语言并发处理的使用详解的文章就介绍到这了,更多相关Go语言 并发处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • GoLang channel使用介绍

    GoLang channel使用介绍

    Channel 和 goroutine 的结合是 Go 并发编程的大杀器。而 Channel 的实际应用也经常让人眼前一亮,通过与 select,cancel,timer 等结合,它能实现各种各样的功能。接下来,我们就要梳理一下 channel 的应用
    2022-10-10
  • Golang使用Channel组建高并发HTTP服务器

    Golang使用Channel组建高并发HTTP服务器

    Golang 作为一门高效的语言,在网络编程方面表现也非常出色,这篇文章主要介绍了如何使用 Golang 和 Channel 组建高并发 HTTP 服务器,感兴趣的可以了解一下
    2023-06-06
  • 详解为什么说Golang中的字符串类型不能修改

    详解为什么说Golang中的字符串类型不能修改

    在接触Go这么语言,可能你经常会听到这样一句话。对于字符串不能修改,可能你很纳闷,日常开发中我们对字符串进行修改也是很正常的,为什么又说Go中的字符串不能进行修改呢?本文就来通过实际案例给大家演示一下
    2023-03-03
  • Go语言中println和fmt.Println区别

    Go语言中println和fmt.Println区别

    本文主要介绍了Go语言中println和fmt.Println区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • Go库text与template包使用示例详解

    Go库text与template包使用示例详解

    这篇文章主要为大家介绍了Go库text与template包使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • Go语言标准库sync.Once使用场景及性能优化详解

    Go语言标准库sync.Once使用场景及性能优化详解

    这篇文章主要为大家介绍了Go语言标准库sync.Once使用场景及性能优化详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • go语言使用Casbin实现角色的权限控制

    go语言使用Casbin实现角色的权限控制

    Casbin是用于Golang项目的功能强大且高效的开源访问控制库。本文主要介绍了go语言使用Casbin实现角色的权限控制,感兴趣的可以了解下
    2021-06-06
  • Golang 中的 json 编解码深度解析

    Golang 中的 json 编解码深度解析

    本文带领大家重新认识Golang中的json编解码,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-10-10
  • Go到底能不能实现安全的双检锁(推荐)

    Go到底能不能实现安全的双检锁(推荐)

    这篇文章主要介绍了Go到底能不能实现安全的双检锁,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-05-05
  • golang中time包之时间间隔格式化和秒、毫秒、纳秒等时间戳格式输出的方法实例

    golang中time包之时间间隔格式化和秒、毫秒、纳秒等时间戳格式输出的方法实例

    时间和日期是我们编程中经常会用到的,下面这篇文章主要给大家介绍了关于golang中time包之时间间隔格式化和秒、毫秒、纳秒等时间戳格式输出的方法实例,需要的朋友可以参考下
    2022-08-08

最新评论