本文首发于公众号:Hunter后端

原文链接:Golang基础笔记十之goroutine和channel

这一篇介绍 Golang 里的 goroutine 和 channel 通道。

以下是本篇笔记目录:

  1. goroutine
  2. channel
  3. 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的更多相关文章

  1. golang语言并发与并行——goroutine和channel的详细理解(一)

    如果不是我对真正并行的线程的追求,就不会认识到Go有多么的迷人. Go语言从语言层面上就支持了并发,这与其他语言大不一样,不像以前我们要用Thread库 来新建线程,还要用线程安全的队列库来共享数据. ...

  2. golang语言并发与并行——goroutine和channel的详细理解

    如果不是我对真正并行的线程的追求,就不会认识到Go有多么的迷人. Go语言从语言层面上就支持了并发,这与其他语言大不一样,不像以前我们要用Thread库 来新建线程,还要用线程安全的队列库来共享数据. ...

  3. golang语言并发与并行——goroutine和channel的详细理解(一) 转发自https://blog.csdn.net/skh2015java/article/details/60330785

    如果不是我对真正并行的线程的追求,就不会认识到Go有多么的迷人. Go语言从语言层面上就支持了并发,这与其他语言大不一样,不像以前我们要用Thread库 来新建线程,还要用线程安全的队列库来共享数据. ...

  4. Golang基础笔记

    <基础> Go语言中的3个关键字用于标准的错误处理流程: defer,panic,recover. 定义一个名为f 的匿名函数: Go 不支持继承和重载. Go的goroutine概念:使 ...

  5. C#基础笔记(第十天)

    C#基础笔记(第十天) 1.字段.属性.方法.构造函数字段:存储数据属性:保护字段,对字段的取值和设值进行限定方法:描述对象的行为构造函数:初始化对象(给对象的每个属性依次的赋值)类中成员,如果不加访 ...

  6. shell基础二十篇 一些笔记

    shell基础二十篇 转自 http://bbs.chinaunix.net/thread-452942-1-1.html 研讨:Bash 内建命令 read (read命令更具体的说明见博客收藏的一 ...

  7. Go基础--goroutine和channel

    goroutine 在go语言中,每一个并发的执行单元叫做一个goroutine 这里说到并发,所以先解释一下并发和并行的概念: 并发:逻辑上具备同时处理多个任务的能力 并行:物理上在同一时刻执行多个 ...

  8. Golang学习笔记:channel

    channel channel是goroutine之间的通信机制,它可以让一个goroutine通过它给另一个goroutine发送数据,每个channel在创建的时候必须指定一个类型,指定的类型是任 ...

  9. Golang基础教程

    以下使用goland的IDE演示,包含总计的golang基础功能共20个章节 一.go语言结构: 二.go基础语法: 三.变量 四.常量 五.运算符 六.条件语句 七.循环 八.函数 九.变量作用域 ...

  10. GoLang基础数据类型--->字典(map)详解

    GoLang基础数据类型--->字典(map)详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.   可能大家刚刚接触Golang的小伙伴都会跟我一样,这个map是干嘛的,是 ...

随机推荐

  1. @PathVaribale

    /** * @pathVaribale * 作用: 用于获取url 中的占位符的值. * 例如:请求 url 中 /delete/{id},这个{id}就是 url 占位符. * url 支持占位符是 ...

  2. mybatis——分页插件PageHelper的使用

    项目开发中涉及列表查询时,经常会需要对查询结果进行分页处理:常用的一个插件--PageHelper,是国内非常优秀的一款开源的mybatis分页插件,它支持基本主流与常用的数据库,一致支持mysql. ...

  3. edge浏览器新版,开心的扔掉chrome!还是微软更良心!windows系统全球用也没说啥!让你android能!

    直接上图吧,这下google慌了吧!微软给力呀!关键是版本直接最新的chromium!比起qq浏览器70,360浏览器78新的多了: 微软开发,质量信得过,就个人隐私之类的我更相信微软,比起googl ...

  4. java模块——使用 47M 的java环境运行HelloWorld

    前言 我们知道,运行java程序需要jre或jdk环境,但是现在的jdk安装包已经很大了,如果我们的程序很简单,并且需要把程序发送给其他没有jdk环境的人的运行要如何做呢?如何精简我们的程序包呢? 从 ...

  5. FastAPI-请求参数与验证

    最近想搞一下接口, 希望能简单上手, 前后端分离, 大致看了一遍 SpringBoot, Gin, NodeJs, Flask, Django, FastAPI 等, 感觉还是用 Python 语言来 ...

  6. WPF 窗口 触摸失效 的一种场景

    最近,生产线反馈,在执行生产大屏测试软件的时候,软件大概率出现不能触摸,但是可以用鼠标的的情况.刚好 这个软件又是WPF 做的,所以做了以下排查. .Net 环境: .NetFrameWork 4.8 ...

  7. odoo15接口调用qweb打印,将pdf旋转并下载到本地

    一.将pdf旋转的通用方法 def rotate_pdf(self, pdf, angle): """ rotateClockwise(90) 这里的pdf传:bytes ...

  8. 如何用Leangoo破解需求隔离与频繁变更的协作困局?

    作为一位经历过"需求文档满天飞.系统各自为战"的研发负责人,我深知团队在需求频繁变更时面临的痛点--信息割裂导致响应滞后.优先级混乱引发返工.协作低效拖慢交付节奏. 近期,我深度测 ...

  9. PyQt实现跨平台毛玻璃背景(全网首发)

    找了很久,大部分都需要调用 win32 API 无法跨平台,终于找到啦 项目地址 安装: python -m pip install BlurWindow 简单例子 import sys from P ...

  10. 一文速通 Python 并行计算:12 Python 多进程编程-进程池 Pool

    一文速通 Python 并行计算:12 Python 多进程编程-进程池 Pool 摘要: 在Python多进程编程中,Pool类用于创建进程池,可并行执行多个任务.通过map.apply等方法,将函 ...