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)。

相关推荐
Rust研习社2 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒2 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro3 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax4 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH4 小时前
Koa和Express的区别
后端
MariaH4 小时前
Koa框架的使用
后端
luckdewei5 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某6 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy6 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom6 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github