Go语言 管道1

本篇文章主要介绍Go语言 无缓冲管道和有缓冲管道概念,特点及其使用示例。

目录

无缓冲通道

有缓冲的管道

语法

特点

代码示例

未分配空间示例

读取次数不一致示例

For-range遍历

总结


无缓冲通道

sync.RWMutex{}

当涉及到多go程时,c语言使用互斥量,上锁来保持资源同步,免资源竞争问题

go语言也支持这种方式,但是go语言更好的解决方案是使用管道、通道

使用管道不需要我们去进行加解锁

A 往管道里面写数据 B从管道里面读数据,go自动帮我们做好了数据同步。

示例如下:

Go 复制代码
package main

import (
   "fmt"
   "time"
)

func main() {
   // 创建管道:创建一个装数字的管道 ==》 channel
   // strChan := make(chan string) // 装字符串的管道

   // 装数字的管道,使用管道的时候一定要make,同map一样,否则nil
   // 此时是无缓冲的管道
   numChan := make(chan int)

   // 创建两个go程,父写数据,子读数据
   go func() {
      for i := 0; i < 50; i++ {
         data := <-numChan
         fmt.Println("data:", data)
      }
   }()

   for i := 0; i < 50; i++ {
      // 向管道中写入数据
      numChan <- i
      fmt.Println("===> 主go程,写入数据:", i)
   }

   time.Sleep(5 * time.Second)
}

运行结果:

bash 复制代码
data: 0
===> 主go程,写入数据: 0
===> 主go程,写入数据: 1
data: 1
data: 2
===> 主go程,写入数据: 2
===> 主go程,写入数据: 3
data: 3
data: 4
===> 主go程,写入数据: 4
===> 主go程,写入数据: 5
data: 5
data: 6
===> 主go程,写入数据: 6
===> 主go程,写入数据: 7

写入和读取无规律,可能读在写的前面打印出来。

有缓冲的 管道

语法

numsChan := make(chan int,10)

特点

1.当缓冲写满的时候,写阻塞,当被读取后,再恢复写入

2.当缓冲区读取完毕,读阻塞

3.如果管道没有使用make分配空间,那么管道默认是ni1的,读取、写入都会阻塞

4.对一个管道,读与写次数必须对等

代码示例

通过创建一个数字管道,主go程和一个子go程写入数据,另一个子go程读取数据。

需要注意写入数据的总条数与读取数据的总条数相等。

示例如下:

Go 复制代码
package main

import (
   "fmt"
   "time"
)

func main() {
   // 有缓冲的管道
   numChan := make(chan int, 10)

   go func() {
      for i := 0; i < 50; i++ {
         // 从管道中读取数据
         data := <- numChan
         fmt.Println("子go程1 读取数据 ==》 data:", data)
      }
   }()

   go func() {
      for i := 0; i < 20; i++ {
         // 从管道中读取数据
         numChan <- i
         fmt.Println("子go程2 写入数据:", i)
      }
   }()

   for i := 20; i < 50; i++ {
      // 向管道中写入数据
      numChan <- i
      fmt.Println("===> 主go程,写入数据:", i)
   }

   time.Sleep(5 * time.Second)
}

运行结果:

bash 复制代码
===> 主go程,写入数据: 20
===> 主go程,写入数据: 21
===> 主go程,写入数据: 22
===> 主go程,写入数据: 23
===> 主go程,写入数据: 24
===> 主go程,写入数据: 25
子go程2 写入数据: 0
子go程2 写入数据: 1
子go程2 写入数据: 2
子go程2 写入数据: 3
===> 主go程,写入数据: 26
子go程1 读取数据 ==》 data: 20
子go程1 读取数据 ==》 data: 0
子go程1 读取数据 ==》 data: 21
子go程1 读取数据 ==》 data: 22
子go程1 读取数据 ==》 data: 23
子go程1 读取数据 ==》 data: 24
子go程1 读取数据 ==》 data: 25
子go程1 读取数据 ==》 data: 26
子go程1 读取数据 ==》 data: 1
子go程1 读取数据 ==》 data: 2

未分配空间示例

如果管道不分配空间直接使用,会怎么样呢?

示例如下:

Go 复制代码
package main

import (
   "fmt"
   "time"
)

func main() {
   var names chan string // 默认是nil的
   go func() {
      fmt.Println("names:", names)
   }()

   names <- "hello world"
   time.Sleep(1 * time.Second)
}

运行结果:

bash 复制代码
$ go run 不分配空间.go
names: <nil>
fatal error: all goroutines are asleep - deadlock!

解决方法进行分配空间

示例如下:

Go 复制代码
package main

import (
   "fmt"
   "time"
)

func main() {

   //var names chan string // 默认是nil的
   names := make(chan string, 10)

   go func() {
      fmt.Println("names:", names)
   }()

   names <- "hello world" // 由于names是nil的,写操作会阻塞在这里
   time.Sleep(1 * time.Second)
}

读取次数不一致示例

读,当主程序被管道阻塞时,那么程序将锁死崩溃

要求我们一定要读写次数保持一致

示例如下:

Go 复制代码
package main

import (
   "fmt"
   "time"
)

func main() {

   numsChan1 := make(chan int, 20)

   //写
   go func() {
      for i := 0; i < 15; i++ {
         // 向管道中写入数据
         numsChan1 <- i
         fmt.Println("===> 子go程,写入数据:", i)
      }
   }()


   // 读
   for i := 0; i < 20; i++ {
      // 从管道中读取数据
      data := <- numsChan1
      fmt.Println("主go程 读取数据 ==》 data:", data)
   }

   time.Sleep(5 * time.Second)
}

当管道的读写次数不一致的时候

如果阻塞在主go程,那么程序会崩溃

如果阻塞在子go程,那么会出现内存泄漏

For-range遍历

避免出现读写不一致,直接使用for-range写法。

示例如下:

Go 复制代码
numsChan2 := make(chan int, 20)

//写
go func() {
   for i := 0; i < 15; i++ {
      // 向管道中写入数据
      numsChan2 <- i
      fmt.Println("===> 子go程,写入数据:", i)
   }
   fmt.Println("数据全部写入完毕,准备关闭管道")
   close(numsChan2)
}()


// 读
// 遍历管道时,只返回一个值
// for range并不知道管道是否已经写完了,所以会一直在这里等待
// 在写入端,将管道关闭,for range遍历关闭管道时,会退出
for v := range numsChan2{
   fmt.Println("读取数据:", v)
}

time.Sleep(5 * time.Second)

总结

1.当管道写满了,写阻塞;

2.当缓冲区读完了,读阻塞;

3.如果管道没有使用make分配空间,管道默认是nil;

4.从nil管道读取数据,写入数据,都会阻塞(注意,不会崩溃);

5.从一个已经colse的管道读取数据时,会返回零值(不会崩溃);

6.像一个已经colse的管道写数据时,会崩溃;

7.关闭一个已经colse的管道,程序会崩溃;

8.关闭管道的动作,一定要在写端执行,不应该放到读端,否则写的继续写会崩溃;

9.读和写的次数一定要对等,否则:

在多个go程中:资源泄露;

在主go程中,程序崩溃(deadlock)。

相关推荐
Oneslide4 小时前
机械革命 单系统纯净重装Ubuntu(全盘覆盖,清空原有Windows)
后端
GetcharZp4 小时前
告别OOM!用Go+libvips实现30000×50000超大图片的流式瓦片服务
后端·go
IT_陈寒5 小时前
JavaScript项目实战经验分享
前端·人工智能·后端
用户47949283569156 小时前
6w star,GitHub 趋势第一的 Ponytail,这个agent插件到底在火什么
前端·后端
神奇小汤圆7 小时前
2026一线大厂Java八股文精选(附答案,高质量整理)
后端
Warson_L8 小时前
LangGraph入门学习资料
后端
神奇小汤圆8 小时前
Spring Boot → Solon 注解迁移实战指南:一张对照表说清楚
后端
kfaino8 小时前
码农的AI翻身(四)你好,我叫 Attention
人工智能·后端
lwx572808 小时前
探秘InnoDB:搞懂它的内存、线程、磁盘与日志刷盘策略
java·后端
云技纵横9 小时前
Spring Boot Actuator 被打穿:线上开了这些端点,等于裸奔
后端