Golang基础笔记十之goroutine和channel
本文首发于公众号:Hunter后端
这一篇介绍 Golang 里的 goroutine 和 channel 通道。
以下是本篇笔记目录:
- goroutine
- channel
- goroutine 与 channel 的使用
1、goroutine
goroutine 是一种轻量级线程(用户态线程),由 Go 运行时管理而非操作系统,它是 Go 并发模型的核心,能高效处理大量并发任务。
1. goroutine 的使用
goroutine 的使用非常简单,直接使用 go + 函数 即可启动一个 goroutine:
package main
import (
"fmt"
"time"
)
func PrintGoroutineInfo() {
fmt.Println("msg from goroutine")
}
func main() {
go PrintGoroutineInfo()
time.Sleep(1 * time.Millisecond)
fmt.Println("msg from main")
}
2. 匿名函数使用 goroutine
func main() {
go func() {
fmt.Println("msg from goroutine")
}()
time.Sleep(1 * time.Second)
}
而如果 goroutine 运行的函数有返回值,想要获取函数的返回值应该如何操作呢?
或者当我们使用 goroutine 的时候,如何使主 goroutine 等待并发的 goroutine 执行完毕再接着往后执行呢?
其中一个方法就是使用 channel 来获取返回值,以及使用 channel 的阻塞状态来等待并发的 goroutine 执行完毕。
2、channel
channel,即通道,可用于在 goroutine 之间传递数据和同步状态。
1. channel 的声明与创建
channel 是强类型的,每个 channel 只能传递一种类型的数据。
1) 无缓冲通道
比如我们声明一个传递 int 类型的 channel:
var ch chan int
或者直接创建一个传递 int 数据的通道:
ch := make(chan int)
2) 有缓冲通道
在创建 channel 的时候,如果不指定容量,那么则称其为无缓冲通道,如果指定了容量,则为有缓冲通道,比如下面创建一个容量为 3 的通道:
ch := make(chan int, 3)
2. channel 的操作
发送数据
向一个 channel 发送数据的操作如下:
ch <- 21
接收数据
从一个 channel 中接收数据的操作如下:
x := <-ch
或者仅仅是接收数据但不使用,可以直接丢弃:
<-ch
使用 range 遍历 channel
也可以使用 range 的方式遍历从 channel 中接收数据,但是需要在通道关闭后:
for x := range ch {
fmt.Println(x)
}
关闭 channel
关闭一个 channel 的操作如下:
close(ch)
3、goroutine 与 channel 的使用
下面介绍几种 channel 在使用中的特殊情况。
1. 阻塞情况
对于 channel 的使用,如果使用不慎,有可能会造成阻塞,以下是几种阻塞的情况
1) 无缓冲通道
对于无缓冲通道而言,发送和接收的操作必须同时发生,否则会进入阻塞状态。
func CapZeroChannel(ch chan int) {
time.Sleep(5 * time.Second)
ch <- 1
fmt.Println("inner func, send msg:", time.Now().Format("2006-01-02 15:04:05"))
}
func main() {
ch := make(chan int)
go CapZeroChannel(ch)
fmt.Println("before func:", time.Now().Format("2006-01-02 15:04:05"))
x := <-ch
fmt.Println("after func:", time.Now().Format("2006-01-02 15:04:05"))
fmt.Println(x)
}
在上面的操作中,最后输出的结果如下:
before func: 2025-06-28 23:33:03
inner func, send msg: 2025-06-28 23:33:08
after func: 2025-06-28 23:33:08
可以看到,在并发的 CapZeroChannel() 函数中,发送数据前等待了 5 秒钟,同时在主 goroutine,也就是 main 函数中,通道接收值的地方发生了阻塞,直到发送方把数据通过 channel 发送过来,才接着往后执行。
而如果我们将等待的地方放在接收前:
func CapZeroChannel(ch chan int) {
ch <- 1
fmt.Println("inner func, send msg:", time.Now().Format("2006-01-02 15:04:05"))
}
func main() {
ch := make(chan int)
go CapZeroChannel(ch)
fmt.Println("before func:", time.Now().Format("2006-01-02 15:04:05"))
time.Sleep(5 * time.Second)
x := <-ch
fmt.Println("after func:", time.Now().Format("2006-01-02 15:04:05"))
fmt.Println(x)
}
最后输出的信息如下:
before func: 2025-06-28 23:37:32
after func: 2025-06-28 23:37:37
inner func, send msg: 2025-06-28 23:37:37
可以看到发送的地方也同时发生了阻塞。
通过这两个示例,可以证实前面的观点,即 对于无缓冲通道而言,发送和接收的操作必须同时发生,否则会进入阻塞状态。
2) 有缓冲通道
对于有缓冲通道来说,阻塞的情况会发生在向已满的通道里发送数据,或者从空的通道里接收数据,下面是各自的示例:
向已满的通道里发送数据
当我们向已满的通道里发送数据,会发生阻塞,下面是代码示例:
func SendMsgToChannel(ch chan int) {
ch <- 1
fmt.Println("send msg to channel: ", 1, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 2
fmt.Println("send msg to channel: ", 2, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 3
fmt.Println("send msg to channel: ", 3, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 4
fmt.Println("send msg to channel: ", 4, " at: ", time.Now().Format("2006-01-02 15:04:05"))
close(ch)
}
func main() {
ch := make(chan int, 3)
go SendMsgToChannel(ch)
time.Sleep(5 * time.Second)
for x := range ch {
fmt.Println("receive msg from channel: ", x, " at: ", time.Now().Format("2006-01-02 15:04:05"))
}
}
可以看到输出的信息如下:
send msg to channel: 1 at: 2025-06-29 00:36:24
send msg to channel: 2 at: 2025-06-29 00:36:24
send msg to channel: 3 at: 2025-06-29 00:36:24
receive msg from channel: 1 at: 2025-06-29 00:36:29
receive msg from channel: 2 at: 2025-06-29 00:36:29
receive msg from channel: 3 at: 2025-06-29 00:36:29
receive msg from channel: 4 at: 2025-06-29 00:36:29
send msg to channel: 4 at: 2025-06-29 00:36:29
前面向通道里发送三条数据,把有缓冲通道占满了,但是在接收前 sleep 了五秒钟,所以没有及时从通道里接收数据,当向通道里发送第四条数据的时候就会发生阻塞。
五秒钟后,主 goroutine 开始从 channel 里消费数据,第四条数据才往里写入。
从空通道里接收数据
如果从空通道里接收数据,也会发生阻塞,下面是代码示例:
func SendMsgToChannel(ch chan int) {
time.Sleep(3 * time.Second)
ch <- 1
fmt.Println("send msg to channel: ", 1, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 2
fmt.Println("send msg to channel: ", 2, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 3
fmt.Println("send msg to channel: ", 3, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 4
fmt.Println("send msg to channel: ", 4, " at: ", time.Now().Format("2006-01-02 15:04:05"))
close(ch)
}
func main() {
ch := make(chan int, 3)
go SendMsgToChannel(ch)
fmt.Println("start receive msg from channel at: ", time.Now().Format("2006-01-02 15:04:05"))
for x := range ch {
fmt.Println("receive msg from channel: ", x, " at: ", time.Now().Format("2006-01-02 15:04:05"))
}
}
其输出信息如下:
start receive msg from channel at: 2025-06-29 00:41:24
receive msg from channel: 1 at: 2025-06-29 00:41:27
send msg to channel: 1 at: 2025-06-29 00:41:27
send msg to channel: 2 at: 2025-06-29 00:41:27
send msg to channel: 3 at: 2025-06-29 00:41:27
send msg to channel: 4 at: 2025-06-29 00:41:27
receive msg from channel: 2 at: 2025-06-29 00:41:27
receive msg from channel: 3 at: 2025-06-29 00:41:27
receive msg from channel: 4 at: 2025-06-29 00:41:27
可以看到,在发送信息前 sleep 了 3 秒,因此接收方也等待了 3 秒才能开始接收数据,在这之前一直是处于阻塞状态。
2. 通道的关闭
通道在关闭后,不可以再向其中发送数据,但是还可以从中接收数据。
func PrintGoroutineInfo(ch chan int) {
fmt.Println("msg from goroutine")
time.Sleep(1 * time.Second)
ch <- 2
close(ch)
}
func main() {
ch := make(chan int)
go PrintGoroutineInfo(ch)
x := <-ch
fmt.Println(x)
}
3. 发送和接收通道的类型
前面我们将通道作为参数传递给函数,其类型仅仅是通道类型,如果我们想要在代码层面使其更严谨,比如某个函数中只允许发送或者接收数据,我们在其类型进行更严谨的声明。
比如发送通道我们可以对其声明为 chan<-,接收通道可以对其声明为 <-chan,下面是代码示例:
func SendMsg(ch chan<- int) {
ch <- 4
fmt.Println("send msg: ")
}
func ReceiveMsg(ch <-chan int) {
x := <-ch
fmt.Println("receive msg: ", x)
}
func main() {
ch := make(chan int, 3)
go SendMsg(ch)
go ReceiveMsg(ch)
time.Sleep(1 * time.Second)
fmt.Println("end")
}
在这篇笔记中,我们介绍 goroutine 和 channel 在 Golang 中的使用,比如如何使用 goroutine 开启一个并发操作,如何使用 channel 在 goroutine 间进行通信。
但是关于 goroutine 之间的一些并发控制与锁相关的一些概念我们将在之后的笔记中进行更详细的介绍。
Golang基础笔记十之goroutine和channel的更多相关文章
- golang语言并发与并行——goroutine和channel的详细理解(一)
如果不是我对真正并行的线程的追求,就不会认识到Go有多么的迷人. Go语言从语言层面上就支持了并发,这与其他语言大不一样,不像以前我们要用Thread库 来新建线程,还要用线程安全的队列库来共享数据. ...
- golang语言并发与并行——goroutine和channel的详细理解
如果不是我对真正并行的线程的追求,就不会认识到Go有多么的迷人. Go语言从语言层面上就支持了并发,这与其他语言大不一样,不像以前我们要用Thread库 来新建线程,还要用线程安全的队列库来共享数据. ...
- golang语言并发与并行——goroutine和channel的详细理解(一) 转发自https://blog.csdn.net/skh2015java/article/details/60330785
如果不是我对真正并行的线程的追求,就不会认识到Go有多么的迷人. Go语言从语言层面上就支持了并发,这与其他语言大不一样,不像以前我们要用Thread库 来新建线程,还要用线程安全的队列库来共享数据. ...
- Golang基础笔记
<基础> Go语言中的3个关键字用于标准的错误处理流程: defer,panic,recover. 定义一个名为f 的匿名函数: Go 不支持继承和重载. Go的goroutine概念:使 ...
- C#基础笔记(第十天)
C#基础笔记(第十天) 1.字段.属性.方法.构造函数字段:存储数据属性:保护字段,对字段的取值和设值进行限定方法:描述对象的行为构造函数:初始化对象(给对象的每个属性依次的赋值)类中成员,如果不加访 ...
- shell基础二十篇 一些笔记
shell基础二十篇 转自 http://bbs.chinaunix.net/thread-452942-1-1.html 研讨:Bash 内建命令 read (read命令更具体的说明见博客收藏的一 ...
- Go基础--goroutine和channel
goroutine 在go语言中,每一个并发的执行单元叫做一个goroutine 这里说到并发,所以先解释一下并发和并行的概念: 并发:逻辑上具备同时处理多个任务的能力 并行:物理上在同一时刻执行多个 ...
- Golang学习笔记:channel
channel channel是goroutine之间的通信机制,它可以让一个goroutine通过它给另一个goroutine发送数据,每个channel在创建的时候必须指定一个类型,指定的类型是任 ...
- Golang基础教程
以下使用goland的IDE演示,包含总计的golang基础功能共20个章节 一.go语言结构: 二.go基础语法: 三.变量 四.常量 五.运算符 六.条件语句 七.循环 八.函数 九.变量作用域 ...
- GoLang基础数据类型--->字典(map)详解
GoLang基础数据类型--->字典(map)详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 可能大家刚刚接触Golang的小伙伴都会跟我一样,这个map是干嘛的,是 ...
随机推荐
- langchain0.3教程:聊天机器人进阶之方法调用
我们思考一个问题:大语言模型是否能帮我们做更多的事情,比如帮我们发送邮件.默认情况下让大模型帮我们发送邮件,大模型会这样回复我们: 可以看到,大模型无法发送邮件,它只会帮我们生成一个邮件模板,然后让我 ...
- Python合成多个视频为一个脚本
编写背景: 由于线上用户反馈媒体添加页加载时间很长,猜测是由于本地视频内存过大引起,于是编写此脚本以便快速生成内存很大的视频 代码如下: # coding=utf-8 from moviepy.edi ...
- Redis 持久化——混合持久化
1.Redis 持久化--混合持久化 RDB 和 AOF 持久化各有利弊,RDB 可能会导致一定时间内的数据丢失,而 AOF 由于文件较大则会影响 Redis 的启动速度,为了能同时使用 RDB 和 ...
- Selenium反屏蔽处理
Selenium自动化过程,在浏览器内会显示如下字样 当出现此内容时,有些网站就会判定是机器在进行操作,然后网站会加载防机器操作程序,如下图滑块验证 触发反机器操作的原理大概如下 解决方法 具体代码, ...
- 《Deep Learning Inference on Embedded Devices: Fixed-Point vs Posit》(一)
After the success of performing deep learning inference by using an 8-bit precision representation o ...
- 10个 DeepSeek 神级提示词,建议收藏!
在当下人工智能飞速发展的时代,DeepSeek 作为一款功能强大的 AI 工具,能够帮助我们实现各种创意和需求.然而,要充分发挥它的潜力,掌握一些巧妙的提示词至关重要.今天,就为大家精心整理了 15 ...
- Python3_数据类型和变量
Python3_数据类型和变量 一.数据类型 Python有五个标准的数据类型: Numbers(数字) String(字符串) List(列表) Tuple(元组) Dictionary(字典) 在 ...
- CTF实验吧加了料的报错注入
实验吧地址 http://ctf5.shiyanbar.com/web/baocuo/index.php F12审查元素发现源码中的提示是这样一整句的查询 基本确定此题为一个SQL注入 /# = un ...
- golang random string
package main import ( "math/rand" "strings" "testing" "time" ...
- C++ condition_variable 条件变量
本节来了解下C++11 中关于条件变量(condition_variable) 的相关知识,这一部分的内容相信网上已经有了很多的分享,这里仅是对该部分内容学习的记录.总结. 条件变量(conditio ...