Go语言并发实现完整代码示例

 更新时间:2026年09月18日 09:46:39   作者:星星懒得动  
并发是Go语言最核心、最亮眼的特性,Go从语言层面原生支持并发,不像Java/Python等语言需要依赖线程库、协程框架,这篇文章主要介绍了Go语言并发实现的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

并发/并行

并发:多线程程序在单核心的 cpu 上运行,称为并发;

并行:多线程程序在多核心的 cpu 上运行,称为并行。

并发与并行并不相同,并发主要由切换时间片来实现“同时”运行,并行则是直接利用多核实现多线程的运行,Go程序可以设置使用核心数,以发挥多核计算机的能力。

简单比喻:

并发像一个人同时煮饭、洗衣服和打电话,通过任务切换来完成多项工作

并行像三个人分别煮饭、洗衣服和打电话,真正同时进行

go语言:只需要简单几行,可以轻松写出一堆并发函数

实现

go routines

下面是几个小例子,方便理解异步

示例一

package main

import "fmt"

func someFunc(name string){
	fmt.Println(name)
}

func main(){
	someFunc("Lucy")
	fmt.Println("hello")
}

运行结果:

在main函数中调用someFunc函数,主函数会被卡住,直到这个函数执行完毕才会继续,所以会先打印Lucy,然后打印hello

示例二

使用go routine可以使主函数不用等这个函数执行就能结束,主线程可以将任务分叉出去

package main

import "fmt"

func someFunc(name string){
	fmt.Println(name)
}

func main(){
	go someFunc("Lucy")
	go someFunc("Lili")
	fmt.Println("hello")
}

运行结果

然后会发现上面的结果都没有打印,只打印hello。这是因为主进程开了一些子进程,子进程会自己跑起来,执行任务,而主进程把任务派发出去立马会忙自己的事。

示例三

可以给他加个延deng

package main

import "fmt"
import "time"

func someFunc(name string){
	fmt.Println(name)
}

func main(){
	go someFunc("Lucy")
	go someFunc("Lili")
	fmt.Println("hello")
	time.Sleep(time.Second * 2)
}

运行结果

可以看到someFunc的打印,但是多运行几次,可以看到,结果是随机的,因为他们都是异步执行的

小结:go 语言采用的是fork join并发模型,下图能展示他的工作原理,展示了主函数的生命周期

fork 指的是在程序中的任意地方可以将执行的子分支与父节点同时运行
join指的是,在未来的某时刻,这些并发的分支可以合并在一起

channels

简单来说,channel 就像是 goroutine 之间的管道消息队列,让不同的并发执行单元能够安全地交换数据。

channel的主要用途:

  • goroutine之间的通信:channel允许一个goroutine向另一个goroutine发送数据。

  • 同步:channel可以用于控制goroutine的执行顺序,例如等待一个goroutine完成。

创建channel

使用make函数创建,可以指定channel中传递的数据类型以及可选的缓冲区大小。

ch := make(chan int)    // 无缓冲channel
chBuffered := make(chan int, 10) // 有缓冲channel,缓冲区大小为10

发送数据:

使用<-操作符,将数据发送到channel。

ch &lt;- 42   // 将42发送到channel ch

接收数据:

使用<-操作符,从channel中接收数据。

value := &lt;-ch   // 从channel ch接收数据并赋值给value

关闭channel:

使用close函数。关闭后,不能再向channel发送数据,但可以继续接收直到数据被取完。

close(ch)

Channel 关闭的本质变化

关闭 Channel 的效果:

操作关闭前关闭后
发送数据 ch <- data正常(缓冲区未满时)panic
接收数据 <-ch阻塞(无数据时)立即返回零值 + ok=false
再次关闭 close(ch)-panic
range 遍历 for v := range ch正常遍历自动退出循环

关闭 Channel 后的接收行为总结

Channel 状态接收操作 value, ok := <-ch说明
关闭且有数据value=数据, ok=true能正常取出剩余数据
关闭且无数据value=零值, ok=false立即返回零值
未关闭且有数据value=数据, ok=true正常接收
未关闭且无数据阻塞等待直到有数据或 channel 关闭

总结

  • 关闭有数据的 channel:可以继续取出所有剩余数据,取完后返回 (零值, false)

  • 关闭无数据的 channel:立即返回 (零值, false)

  • 关键区别ok 标志告诉你是否真的收到了数据,还是只是因为 channel 关闭而返回的零值

这种设计让 Go 的 channel 能够优雅地实现"数据流结束"的信号传递!

最佳实践

  • 遵循"谁创建,谁关闭"的原则

  • 使用 defer 和 recover 保护可能 panic 的代码

  • 考虑使用 context 进行优雅的 goroutine 生命周期管理

  • 在关闭 channel 前确保没有 goroutine 会再向它发送数据

记住:向已关闭的 channel 发送数据是 Go 中的经典错误,一定要避免!

通道分类

无缓冲channel与有缓冲channel:

  • 无缓冲channel: 发送和接收操作是同步的。发送操作会阻塞,直到另一个goroutine在对应的channel上执行接收操作,反之亦然。这种特性使得无缓冲channel非常适合用于两个goroutine之间的同步。

    • 同步通信:发送和接收必须同时准备好

    • 发送操作会阻塞,直到有 goroutine 接收

    • 接收操作会阻塞,直到有 goroutine 发送

  • 有缓冲channel:发送操作只有在缓冲区满时才会阻塞,接收操作只有在缓冲区空时才会阻塞。有缓冲channel提供了一种异步通信的方式,但要注意缓冲区大小,如果缓冲区满了,发送操作会阻塞。

    • 异步通信:缓冲区未满时可立即发送,未空时可立即接收

    • 只有当缓冲区满时发送才会阻塞

    • 只有当缓冲区空时接收才会阻塞

  • 特性

无缓冲 Channel有缓冲 Channel
同步性强同步弱同步
阻塞时机发送/接收必须配对缓冲区满/空时才阻塞
使用场景精确的goroutine同步生产消费解耦、流量控制
类比电话通话邮件信箱

select

在Go语言中,select语句用于在多个通道操作(发送或接收)之间进行选择。它会一直等待,直到其中一个通道操作可以执行,然后执行该操作。如果多个通道操作都可以执行,它会随机选择一个执行。

select语句是Go语言中处理并发编程的重要工具,它允许goroutine同时等待多个通道操作,从而可以实现多路复用。

基本语法

select {
case sendOrReceive1:
    // 执行相应的操作
case sendOrReceive2:
    // 执行相应的操作
default:
    // 如果以上都没有准备就绪,则执行default
}

特点

  • 随机选择:如果多个case同时就绪,select会随机选择一个执行。

  • 阻塞:如果没有case就绪,且没有default分支,select会一直阻塞,直到某个case就绪。

  • 非阻塞:如果有default分支,当没有case就绪时,会执行default分支,从而避免阻塞。

使用场景

  • 多通道操作:当需要同时从多个通道接收数据,或者向多个通道发送数据时。

  • 超时控制:通过time.After通道实现超时控制。

  • 非阻塞操作:使用default分支实现非阻塞的发送或接收。

package main

import "fmt"

func main(){

	mychannel := make(chan string)
	anotherChannel := make(chan string)

	go func() {
		mychannel &lt;- "data"  //发送数据到channel
	}()

	go func() {
		anotherChannel &lt;- "data 2"  //发送数据到channel
	}()

	// 主goroutine 从channel接收数据
	select {
	case msgFromMychannel := &lt;- mychannel:
		fmt.Println(msgFromMychannel)
	case msgFromAnotherChannel := &lt;- anotherChannel:
		fmt.Println(msgFromAnotherChannel)

	}
	
}

超时控制

func main() {
    ch := make(chan string)
    
    go func() {
        time.Sleep(3 * time.Second)  // 模拟耗时操作
        ch &lt;- "操作完成"
    }()
    
    select {
    case result := &lt;-ch:
        fmt.Println("成功:", result)
    case &lt;-time.After(2 * time.Second):  // 2秒超时
        fmt.Println("操作超时!")
    }
}

运行结果

for-select loop

示例一

package main

import "fmt"

func main(){
	charchannel := make(chan string, 3) //带缓冲
	chars := []string{"a","b","c"}

	for _,s := range chars{
		select{
		case charchannel &lt;-s:
		}
	}
	close(charchannel) //关闭通道之后也能读

	for result := range charchannel {
		fmt.Println(result)
	}
}

运行结果

示例二:无限循环协程

package main

import "fmt"
import "time"

func someFunc(){
	for{
		select{
		default:
			fmt.Println("ongoing")
		}
	}
}

func main() {
	go someFunc()
	time.Sleep(time.Second * 10)

}

运行结果:无线循环打印,但是因为主goroutine 10s退出了,所以终止了:

done channel

示例三

在someFunc中,有一个无限循环,循环中使用select监听两个case:一个是从done中接收数据,另一个是default。 

select语句会非阻塞地检查各个case。如果done通道有数据可读(或者被关闭),那么case <- done这个分支就会执行,然后return退出循环

package main

import "fmt"
import "time"

func someFunc(done &lt;- chan bool){ //将done传递给someFunc
	for{
		select{//非阻塞检查各个case
		case &lt;- done:
			return
		default:
			fmt.Println("ongoing")
		}
	}
}

func main() {
	done := make(chan bool)
	go someFunc(done)
	time.Sleep(time.Second * 3)
	close(done) //关闭done通道

}

示例四

package main
import "fmt"
import "time"

func doWork(done chan bool) {
    fmt.Println("Working...")
    time.Sleep(2 * time.Second)
    fmt.Println("Done!")
    done &lt;- true // 发送完成信号
}

func main() {
    done := make(chan bool)
    go doWork(done)
    &lt;-done // 等待完成信号(阻塞直到收到数据)
    fmt.Println("Work completed!")
}

运行结果: 打印Working之后sleep 2s 打印Done

pipelines

Pipeline(管道) 是 Go 并发编程中的一种重要模式,它通过连接多个阶段的 channel 操作来处理数据流,每个阶段都是一组执行相同函数的 goroutine。

Pipeline 的核心概念

Pipeline 就像工厂的流水线,数据像零件一样在不同工位(阶段)间传递和处理

数据源 → 阶段1 → 阶段2 → ... → 阶段N → 结果

Go Pipeline 的核心价值:

  • 数据流处理:将复杂任务分解为简单的阶段

  • 并发控制:自动处理 goroutine 间的同步

  • 资源管理:通过 channel 控制内存和 CPU 使用

  • 错误处理:统一的错误传播机制

  • 可组合性:像乐高积木一样组合不同阶段

Pipeline 模式让并发编程变得:

  • ✅ 更清晰:每个阶段职责单一

  • ✅ 更安全:自动处理并发问题

  • ✅ 更灵活:易于扩展和修改

  • ✅ 更高效:充分利用多核性能

总结 

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

相关文章

  • Go语言常见错误之滥用getters/setters误区实例探究

    Go语言常见错误之滥用getters/setters误区实例探究

    在Go语言编程中,恰如其分地使用getters和setters是至关重要的,过度和不适当地使用它们可能导致代码冗余、可读性差和封装不当,在本文中,我们将深入探讨如何识别滥用getter和setter的情况,以及如何采取最佳实践来避免这些常见的Go错误
    2024-01-01
  • Go实现值接收者vs指针接收者的区别

    Go实现值接收者vs指针接收者的区别

    本文主要介绍了Go实现值接收者vs指针接收者,用真实踩坑案例讲透两者的语义差异、性能影响和接口实现规则,具有一定的参考价值,感兴趣的可以了解一下
    2026-08-08
  • Golang根据job数量动态控制每秒协程的最大创建数量方法详解

    Golang根据job数量动态控制每秒协程的最大创建数量方法详解

    这篇文章主要介绍了Golang根据job数量动态控制每秒协程的最大创建数量方法
    2024-01-01
  • Golang编写自定义IP限流中间件的方法详解

    Golang编写自定义IP限流中间件的方法详解

    这篇文章给大家详细的介绍了Golang编写自定义IP限流中间件的方法,文章通过代码实例介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2023-09-09
  • GO中GORM 使用教程

    GO中GORM 使用教程

    GORM是Go语言的ORM库,它将关系型数据库表与Go语言结构体映射,提供便捷的数据库操作,本文就来介绍有下GO中GORM 使用教程,具有一定的参考价值,感兴趣的可以了解一下
    2025-02-02
  • Golang中interface{}转为数组的操作

    Golang中interface{}转为数组的操作

    这篇文章主要介绍了Golang中interface{}转为数组的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • GO中优雅编码与降低圈复杂度详析

    GO中优雅编码与降低圈复杂度详析

    Go语法简单易用,有其他编程经验的开发者,相信学习并快速上手Go语言的开发,多数觉得不困难吧,下面这篇文章主要给大家介绍了关于GO中优雅编码与降低圈复杂度的相关资料,需要的朋友可以参考下
    2022-12-12
  • Golang内存管理之内存分配器详解

    Golang内存管理之内存分配器详解

    Go内存分配器的设计思想来源于TCMalloc,全称是Thread-Caching Malloc,核心思想是把内存分为多级管理,下面就来和大家深入聊聊Go语言内存分配器的使用吧
    2023-06-06
  • Kotlin编程基础语法编码规范

    Kotlin编程基础语法编码规范

    这篇文章主要为大家介绍了Kotlin编程条件控制示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • golang 设置web请求状态码操作

    golang 设置web请求状态码操作

    这篇文章主要介绍了golang 设置web请求状态码操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12

最新评论