Go 语言怎么通过通信共享内存?

简介: Go 语言怎么通过通信共享内存?

介绍

Go 语言使用 goroutinechannel,可以实现通过通信共享内存。

本文我们介绍 Go 语言怎么通过通信共享内存。

goroutinechannel

在了解 Go 语言怎么通过通信共享内存之前。我们需要先了解一些预备知识,即 goroutinechannel 是什么?

goroutine 具有简单的模型:它是与其它 goroutine并发运行在同一地址空间的函数。

goroutine 是轻量级的,所有消耗几乎就只有栈空间的分配。而且栈最开始是非常小的,所以他们很廉价,仅在需要时才会随着堆空间的分配(和释放)而变化。

摘自「Effective Go - channels[1]」。

注意:goroutine 之所以取名为 goroutine,是因为现有的术语 - 线程、协程、进程等等 - 无法准确传达它的含义。也有些资料将 goroutine 翻译为 Go 协程或 Go 程。

使用 goroutine 也非常简单,在函数或方法前添加 go 关键字,即可在新的 goroutine 中调用它。当调用完成后,该 goroutine 也会安静地退出。

此外,匿名函数也可以在 goroutine 中调用。

关于 goroutine 的实现原理和调度器模型 GPM,感兴趣的读者朋友们可以自行查阅相关资料。

我们已了解,什么是 goroutine,以及怎么使用 goroutine 调用函数或方法、匿名函数。

但是,想要实现 goroutine 之间的通信,我们还需要了解 channel

channel 需要使用内置函数 make 分配内存,其结果值充当了对底层数据结构的引用。如果提供了一个可选的参数,它就会为该 channel 设置缓冲区大小,否则,该 channel 则为无缓冲区的 channel

关于 channel 的实现原理,感兴趣的读者朋友们可以阅读「Golang 语言中的 channel 实现原理」。

需要注意的是,两个 goroutine 之间通过无缓冲区的 channel 通信时,同步交换数据。

作为两个 goroutine 之间的通信管道,向 channel 中发送数据的 goroutine 称为“发送者”,反之,从 channel 中接收数据的 goroutine 称为“接收者”。

03

通过通信共享内存

我们已经基本了解 Go 语言的 goroutinechannel,接下来我们看一下两个 goroutine 之间怎么使用 channel (无缓冲区和缓冲区)进行通信?

示例代码:

func main() {
 c := make(chan int) // 定义一个无缓冲区 channel
 go func() {         // 启动一个 goroutine 调用匿名函数
  fmt.Println("启动一个 goroutine 调用匿名函数")
  c <- 1 // 该 goroutine 向 channel 发送一个值(信号)
 }()
 fmt.Println("main 函数")
 out := <-c // main goroutine 从 channel 中接收一个值(信号),再未接收到值(信号)之前,一直阻塞
 fmt.Println(out) // 该打印无实际意义,仅为了读者容易理解
}

阅读上面这段代码,我们定义一个无缓冲区 channel,执行匿名函数的 goroutine 作为发送者,main goroutine 作为接收者。

需要注意的是,无缓冲区 channel,接收者在收到值之前,发送者会一直阻塞。同理,发送者在发送值之前,接收者也会一直阻塞。

示例代码:

func main() {
  // c := make(chan int) // 无缓冲区 channel
 c := make(chan int, 5) // 缓冲区 channel
 for i := 0; i < 20; i++ {
  c <- 1
  go func() {
   fmt.Println("do something:", i)
   <-c
  }()
 }
 time.Sleep(time.Second * 2) // 为了防止 main goroutine 提前退出
}

阅读上面这段代码,我们定义一个缓冲区大小为 5 的 channel,执行匿名函数的 goroutine 作为接收者,main goroutine 作为发送者。

需要注意的是,该段代码中有 5 个执行匿名函数的 goroutine,即 N 个接收者,1 个发送者(main goroutine)。

我们前面讲过,接收者在收到值之前会一直阻塞,而无缓冲区 channel 在接收者收到值之前,发送者会一直阻塞。

如果我们将上面这段代码中的缓冲区 channel 换成无缓冲区 channelN - 1 个接收者在接收到值之前,发送者会一直阻塞,发送者阻塞,导致接收者一直接收不到值,也会一直阻塞,从而导致死锁。

上面这段话有些拗口,读者朋友们可以通过运行使用无缓冲区 channel 的代码来帮助自己理解。

我们运行使用缓冲区大小为 5 的 channel 的代码,发现代码可以正常运行,发送者和接收者之间不会产生死锁。

这是因为缓冲区 channel,发送者仅在值被复制到缓冲区之前阻塞,如果缓冲区已满,发送者会一直阻塞,直到某个接收者取出一个值。

回到上面这段示例代码中,执行匿名函数的 N 个 goroutine 作为接收者,在没有收到 main goroutine 发送的数据之前,一直处于阻塞状态,直到作为发送者的 main goroutine 发送数据到缓冲区 channel 中。

读者朋友们如果仔细阅读这段代码,会发现上面这段代码虽然不会产生死锁,但是存在一个 bug

解决方案可以阅读我们之前的一篇文章「Go 语言使用 goroutine 运行闭包的“坑”」,限于篇幅,我就不在本文中赘述了。

04

总结

本文我们介绍 Go 语言中,什么是 goroutinechannel,其中 channel 分为无缓冲区和缓冲区。

在简单了解 goroutinechannel 后,我们又介绍怎么使用 channel,实现两个 goroutine 之间通信。


目录
相关文章
|
17天前
|
JSON 测试技术 Go
零值在go语言和初始化数据
【7月更文挑战第10天】本文介绍在Go语言中如何初始化数据,未初始化的变量会有对应的零值:bool为`false`,int为`0`,byte和string为空,pointer、function、interface及channel为`nil`,slice和map也为`nil`。。本文档作为指南,帮助理解Go的数据结构和正确使用它们。
68 22
零值在go语言和初始化数据
|
2天前
|
缓存 安全 编译器
Go语言的goroutine是基于什么线程模型实现的
Go语言的goroutine是基于什么线程模型实现的?
12 3
|
8天前
|
JSON 中间件 Go
Go语言Web框架Gin介绍
【7月更文挑战第19天】Gin是一个功能强大、高性能且易于使用的Go语言Web框架。它提供了路由、中间件、参数绑定等丰富的功能,帮助开发者快速构建高质量的Web应用。通过本文的介绍,你应该对Gin框架有了初步的了解,并能够使用它来开发简单的Web服务。随着你对Gin的深入学习和实践,你将能够利用它构建更复杂、更强大的Web应用。
|
13天前
|
Cloud Native Java Go
为什么要学习Go语言?
GO logo的核心理念,即简单胜于复杂。使用现代斜体无衬线字体与三条简单的运动线相结合,形成一个类似于快速运动的两个轮子的标记,传达速度和效率。字母的圆形暗示了GO地鼠的眼睛,创造了一个熟悉的形状,让标记和吉祥物很好地搭配在一起。
27 4
|
17天前
|
存储 Go
go语言中fmt格式化包和内置函数汇总
【7月更文挑战第10天】本文介绍fmt包和`Errorf`用于创建格式化的错误消息。`fmt`包还涉及一些接口,如`Formatter`、`GoStringer`、`ScanState`、`Scanner`和`Stringer`,支持自定义格式化和输入/输出处理。
25 1
|
17天前
|
Go
go语言中格式化输出的占位符
【7月更文挑战第10天】`fmt` 包在 Go 语言中用于格式化输出,包括不同类型的占位符:%v(默认格式)、%+v(带字段名的结构体)、%#v(Go语法表示)、%T(类型表示)、%%(百分号)。布尔值用%t,整数有%b、%c、%d、%o、%q、%x、%X和%U。浮点数和复数用%b、%e、%E、%f、%g、%G。字符串和字节切片用%s、%q、%x、%X。指针用%p。占位符可配合+、-、#、空格和0进行调整。宽度和精度控制输出格式,例如 %.4g 控制小数精度。Go 没有 `%u`,但无符号整数默认打印为正数。运算符包括逻辑、比较、加减、乘除、移位、按位和按位异或等。
23 1
|
8天前
|
Oracle 关系型数据库 MySQL
|
15天前
|
安全 Go
Go语言map并发安全,互斥锁和读写锁谁更优?
Go并发编程中,`sync.Mutex`提供独占访问,适合读写操作均衡或写操作频繁的场景;`sync.RWMutex`允许多个读取者并行,适用于读多写少的情况。明智选择锁可提升程序性能和稳定性。示例展示了如何在操作map时使用这两种锁。
27 0
|
15天前
|
安全 Go 开发者
Go语言map并发安全使用的正确姿势
在Go并发编程中,由于普通map不是线程安全的,多goroutine访问可能导致数据竞态。为保证安全,可使用`sync.Mutex`封装map或使用从Go 1.9开始提供的`sync.Map`。前者通过加锁手动同步,后者内置并发控制,适用于多goroutine共享。选择哪种取决于具体场景和性能需求。
16 0
|
15天前
|
存储 安全 Java
Go语言中的map为什么默认不是并发安全的?
Go语言的map默认不保证并发安全,以优化性能和简洁性。官方建议在需要时使用`sync.Mutex`保证安全。从Go 1.6起,并发读写map会导致程序崩溃,鼓励开发者显式处理并发问题。这样做的哲学是让代码更清晰,并避免不必要的性能开销。
20 0