广告
返回顶部
首页 > 资讯 > 精选 >GO语言中通道和sync包如何使用
  • 383
分享到

GO语言中通道和sync包如何使用

2023-07-05 05:07:13 383人浏览 八月长安
摘要

这篇文章主要讲解了“Go语言中通道和sync包如何使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“GO语言中通道和sync包如何使用”吧!GO通道和 sync 包的分享我们一起回顾一下上次

这篇文章主要讲解了“Go语言中通道和sync包如何使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“GO语言中通道和sync包如何使用”吧!

GO通道和 sync 包的分享

我们一起回顾一下上次分享的内容:

  • GO协程同步若不做限制的话,会产生数据竞态的问题

  • 我们用的方式来解决如上问题,根据使用场景选择使用互斥锁 和 读写锁

  • 比使用锁更好的方式是原子操作,但是使用go的 sync/atomic需要小心使用,因为涉及内存

要是对GO的锁和原子操作还感兴趣的话,欢迎查看文章GO的锁和原子操作分享

上次我们分享到锁和原子操作,都可以保证共享数据的读写

可是,他们还是会影响性能,不过,Go 为开发这提供了 通道 这个神器

通道是什么

是一种特殊的类型,是连接并发goroutine的管道

channel 通道是可以让一个 goroutine 协程发送特定值到另一个 goroutine 协程的通信机制

通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序,这一点和管道是一样的

一个协程从通道的一头放入数据,另一个协程从通道的另一头读出数据

每一个通道都是一个具体类型的导管,声明 channel 的时候需要为其指定元素类型。

通道能做什么

控制协程的同步,让程序有序运行

GO 中提倡 不要通过共享内存来通信,而通过通信来共享内存

goroutine协程 是 Go 程序并发的执行体,channel 通道就是它们之间的连接,他们之间的桥梁,他们的交通枢纽

通道有哪几种

大致可分为如下三种:

  • 无缓冲通道

  • 有缓冲的通道

  • 单向通道

无缓冲通道

GO语言中通道和sync包如何使用

无缓冲的通道又称为阻塞的通道

无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功

两个 goroutine 协程将继续执行

我们反过来看,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个 goroutine 协程在该通道上发送一个数据

因此,无缓冲通道也被称为同步通道,因为我们可以使用无缓冲通道进行通信,利用发送和接收的 goroutine 协程同步化

有缓冲的通道

GO语言中通道和sync包如何使用

还是上述提到的,有缓冲通道,就是在初始化 / 创建通道 的 make 函数的第 2 个参数填上我们所期望的缓冲区大小 , 例如:

ch2 := make(chan int , 4)

此时,该通道的容量为4,发送方可以一直向通道中发送数据,直到通道满,且通道数据未被读走时,发送方就会阻塞

只要通道的容量大于零,那么该通道就是有缓冲的通道

通道的容量表示通道中能存放元素的数量

我们可以使用内置的 len函数 获取通道内元素的数量,使用 cap函数 获取通道的容量

单向通道

通道默认是既可以读有可以写的,但是单向通道就是要么只能读,要么只能写

chan <- int

GO语言中通道和sync包如何使用

是一个只能发送的通道,可以发送但是不能接收

<- chan int

GO语言中通道和sync包如何使用

是一个只能接收的通道,可以接收但是不能发送

如何创建和声明一个通道

声明通道

在 Go 里面,channel是一种类型,默认就是一种引用类型

简单解释一下什么是引用:

  • 在我们写c++的时候,用到引用会比较多

  • 引用,顾名思义是某一个变量或对象的别名,对引用的操作与对其所绑定的变量或对象的操作完全等价

  • 在C++里面是这样用的:

  • 类型 &引用名=目标变量名;

声明一个通道

var 变量名 chan 元素类型var ch2 chan string   // 声明一个传递字符串数据的通道var ch3 chan []int // 声明一个传递int切片数据的通道var ch4 chan bool  // 声明一个传递布尔型数据的通道var ch5 chan interface{}  // 声明一个传递接口类型数据的通道

看,声明一个通道就是这么简单

对于通道来说,关声明了还不能使用,声明的通道默认是其对应类型的零值,例如

  • int 类型 零值 就是 0

  • string 类型 零值就是个 空串

  • bool 类型 零值就是 false

  • 切片的 零值 就是 nil

我们还需要对通道进行初始化才可以正常使用通道哦

初始化通道

一般是使用 make 函数初始化之后才能使用通道,也可以直接使用make函数 创建通道

例如:

ch6 := make(chan string)ch7 := make(chan []int)ch7 := make(chan bool)ch8 := make(chan interface{})

make 函数的第二个参数是可以设置缓冲的大小的,我们来看看源码的说明

// The make built-in function allocates and initializes an object of type// slice, map, or chan (only). Like new, the first argument is a type, not a// value. Unlike new, make's return type is the same as the type of its// argument, not a pointer to it. The specification of the result depends on// the type:// Slice: The size specifies the length. The capacity of the slice is// equal to its length. A second integer argument may be provided to// specify a different capacity; it must be no smaller than the// length. For example, make([]int, 0, 10) allocates an underlying array// of size 10 and returns a slice of length 0 and capacity 10 that is// backed by this underlying array.// Map: An empty map is allocated with enough space to hold the// specified number of elements. The size may be omitted, in which case// a small starting size is allocated.// Channel: The channel's buffer is initialized with the specified// buffer capacity. If zero, or the size is omitted, the channel is// unbuffered.func make(t Type, size ...IntegerType) Type

如果 make 函数的第二个参数不填,那么就默认是无缓冲的通道

现在我们来看看如何操作 channel 通道,都可以怎么玩

如何操作 channel

通道的操作有如下三种操作:

  • 发送(send)

  • 接收(receive)

  • 关闭(close)

对于发送和接收通道里面的数据,写法就比较形象,使用 <- 来指向是从通道里面读取数据,还是从通道中发送数据

向通道发送数据

// 创建一个通道ch := make(chan int)// 发送数据给通道ch <- 1

我们看到箭头的方向是,1 指向了 ch 通道,所以不难理解,这是将1 这个数据,放入通道中

从通道中接收数据

num := <-ch

不难看出,上述代码是 ch 指向了一个需要初始化的变量,也就是说,从 ch 中读出一个数据,赋值给 num

我们从通道中读出数据,也可以不进行赋值,直接忽略也是可以的,如:

<-ch

关闭通道

Go中提供了 close 函数来关闭通道

close(ch)

对于关闭通道非常需要注意,用不好直接导致程序崩溃

  • 只有在通知接收方 goroutine 协程所有的数据都发送完毕的时候才需要关闭通道

  • 通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的

关闭后的通道有以下 4 个特点:

  • 对一个关闭的通道再发送值就会导致 panic

  • 对一个关闭的通道进行接收会一直获取值直到通道为空

  • 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值

  • 关闭一个已经关闭的通道会导致 panic

通道异常情况梳理

我们来整理一下对于通道会存在的异常:

channel 状态未初始化的通道(nil)通道非空通道是空的通道满了通道未满
接收数据阻塞接收数据阻塞接收数据接收数据
发送数据阻塞发送数据发送数据阻塞发送数据
关闭panic关闭通道成功
待数据读取完毕后
返回零值
关闭通道成功
直接返回零值
关闭通道成功
待数据读取完毕后
返回零值
关闭通道成功
待数据读取完毕后
返回零值

每一种通道的DEMO实战

无缓冲通道

func main() {   // 创建一个无缓冲的,数据类型 为 int 类型的通道   ch := make(chan int)   // 向通道中写入 数字 1   ch <- 1   fmt.Println("send successfully ... ")}

执行上述代码我们可以查看到效果

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        F:/my_channel/main.go:9 +0x45
exit status 2

出现上述报错 deadlock 错误的原因,细心的小伙伴应该能够知道为什么,我上述有提到

我们使用 ch := make(chan int) 创建的是无缓冲的通道

无缓冲的通道只有在有接收方接收值的时候才能发送数据成功

我们可以想一下我们生活中的案例一样:

你在某东上买了一个稍微贵重一点的物品,某东快递人员给你寄快递的时候,打电话给你,必须要送到你的手上,不然不敢签收,这个时候,你不方便,或者你不签收,那么这个快递就是算作没有寄送成功

因此,上述问题原因是,创建了一个无缓冲通道,发送方一直在阻塞,通道中一直未有协程读取数据,导致死锁

我们的解决办法就是创建另外一个协程,将数据从通道中读出来即可

package mainimport "fmt"func recvData(c chan int) {ret := <-cfmt.Println("recvData successfully ... data = ", ret)}func main() {// 创建一个无缓冲的,数据类型 为 int 类型的通道ch := make(chan int)go recvData(ch)// 向通道中写入 数字 1ch <- 1fmt.Println("send successfully ... ")}

这里需要注意,如果 go recvData(ch) 放在了 ch <- 1 之后,那么结果还是一样的死锁,原因还是因为 ch <- 1 会一直阻塞,根本不会执行到 他之后的语句

实际效果

recvData successfully ... data =  1
send successfully ...

有缓冲通道

func main() {   // 创建一个无缓冲的,数据类型 为 int 类型的通道   ch := make(chan int , 1)   // 向通道中写入 数字 1   ch <- 1   fmt.Println("send successfully ... ")}

还是同样的案例,同样的代码,我们只是把无缓冲通道,换成了有缓冲的通道, 我们仍然不专门开协程读取通道的数据

实际效果 , 发送成功

send successfully ...

因为此时通道中的缓冲是1,第一次向通道中发送数据,不会阻塞,

可是如果,在通道中数据还未读取出去之前,又向通道中写入数据,则此处会阻塞,

若一直没有协程从通道中读取数据,则结果与上述一样,会死锁

单向通道

package mainimport "fmt"func OnlyWriteData(out chan<- int) {   // 单向 通道 , 只写 不能读   for i := 0; i < 10; i++ {      out <- i   }   close(out)}func CalData(out chan<- int, in <-chan int) {   // out 单向 通道 , 只写 不能读   // int 单向 通道 , 只读 不能写   // 遍历 读取in 通道,若 in通道 数据读取完毕,则阻塞,若in 通道关闭,则退出循环   for i := range in {      out <- i + i   }   close(out)}func myPrinter(in <-chan int) {   // 遍历 读取in 通道,若 in通道 数据读取完毕,则阻塞,若in 通道关闭,则退出循环   for i := range in {      fmt.Println(i)   }}func main() {   // 创建2 个无缓冲的通道   ch2 := make(chan int)   ch3 := make(chan int)   go OnlyWriteData(ch2)   go CalData(ch3, ch2)   myPrinter(ch3)}

我们模拟 2 个通道,

  • 一个 只写 不能读

  • 一个 只读 不能写

实际效果

0
2
4
6
8
10
12
14
16
18

关闭通道

package mainimport "fmt"func main() {   c := make(chan int)      go func() {      for i := 0; i < 10; i++ {         // 循环向无缓冲的通道中写入数据, 只有当上一个数据被读走之后,下一个数据才能往通道中放         c <- i      }      // 关闭通道      close(c)   }()   for {      // 读取通道中的数据,若通道中无数据,则阻塞,若读到 ok 为false, 则通道关闭,退出循环      if data, ok := <-c; ok {         fmt.Println(data)      } else {         break      }   }   fmt.Println("channel over")}

再次强调一下关闭通道,demo 的模拟方式与上述的案例基本一致,感兴趣的可以自己运行看看效果

看到这里,细心的小伙伴应该可以总结出,判断通道是否关闭的 2种 方式了吧?

读取通道的时候,判断bool类型的变量是否为false

例如上述代码

if data, ok := <-c; ok {fmt.Println(data)} else {break}

判断 ok 为true,则正常读取到数据, 若为false ,则通道关闭

通过 for range 的方式来遍历通道,若退出循环,则是因为通道关闭

sync 包

Go 的 sync 包也是用作实现并发任务的同步

还记得吗,在分享 文章GO的锁和原子操作分享的时候,我们就用到过 sync 包

用法大同消息,这里列举一下 sync 包涉及的数据结构和方法

  • sync.WaitGroup

  • sync.Once

  • sync.Map

sync.WaitGroup

他是一个结构体,传递的时候要传递指针 ,这里需要注意

他是并发安全的,内部有维护一个计数器

涉及的方法:

(wg * WaitGroup) Add(delta int)

参数中 传入的 delta ,表示 sync.WaitGroup 内部的计数器 + delta

(wg *WaitGroup) Done()

表示当前协程退出,计数器 -1

(wg *WaitGroup) Wait()

等待并发任务执行完毕,此时的计数器为变成 0

sync.Once

他是并发安全的,内部有互斥锁 和 一个布尔类型的数据

  • 互斥锁 用于加锁解锁

  • 布尔类型的数据 用于记录初始化是否完成

一般用于在高并发的场景下只执行一次,我们一下子就能想到的场景会有程序启动时,加载配置文件的场景

针对类似的场景,Go 也给我们提供了解决方法 ,即 sync.Once 里面的 Do 方法

func (o *Once) Do(f func()) {}

Do 方法的参数 是一个函数,可是我们要在该函数里面传递参数咋整?

可以使用Go 里面的闭包来实现 , 闭包的具体实现方式,感兴趣的可以深入了解一下

sync.Map

他是并发安全的,正是因为 Go 中的 map 是并发不安全的,因此有了 sync.Map

sync.Map 有如下几个明显的优势:

  • 并发安全

  • sync.Map 不需要使用 make 初始化,直接使用 myMap := sync.Map{} 即可使用 sync.Map 里面的方法

sync.Map 涉及的方法

见名知意

Store

存入 key 和value

Load

取出 某个key 对应的 value

LoadOrStore

取出 并且 存入 2个操作

Delete

删除key 和 对应的 value

Range

遍历所有key 和 对应的 value

感谢各位的阅读,以上就是“GO语言中通道和sync包如何使用”的内容了,经过本文的学习后,相信大家对GO语言中通道和sync包如何使用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: GO语言中通道和sync包如何使用

本文链接: https://www.lsjlt.com/news/349964.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • GO语言中通道和sync包如何使用
    这篇文章主要讲解了“GO语言中通道和sync包如何使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“GO语言中通道和sync包如何使用”吧!GO通道和 sync 包的分享我们一起回顾一下上次...
    99+
    2023-07-05
  • GO语言中通道和sync包的使用教程分享
    目录GO通道和 sync 包的分享通道是什么通道能做什么通道有哪几种无缓冲通道有缓冲的通道单向通道如何创建和声明一个通道声明通道初始化通道如何操作 channel通道异常情况梳理每一...
    99+
    2023-02-24
    GO 通道使用 GO sync包使用 GO 通道 GO sync包
  • Go语言单向通道如何实现
    今天小编给大家分享一下Go语言单向通道如何实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Go语言的类型系统提供了单方向的...
    99+
    2023-07-05
  • Go语言包如何使用
    本篇内容介绍了“Go语言包如何使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!包(package)是多个Go源码的集合,是一种高级的代码复...
    99+
    2023-07-04
  • Go语言中flag包如何使用
    今天就跟大家聊聊有关Go语言中flag包如何使用,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。这一章所学的任何代码,都要编译成.exe才能跑,禁止右...
    99+
    2022-10-19
  • Go语言中chan通道有什么作用
    这篇文章主要介绍了Go语言中chan通道有什么作用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Go语言中chan通道有什么作用文章都会有所收获,下面我们一起来看看吧。在Go语言中,通道(chan)是gorou...
    99+
    2023-07-05
  • Go语言如何实现带缓冲的通道
    这篇文章主要介绍Go语言如何实现带缓冲的通道,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!什么是gogo是golang的简称,golang 是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程...
    99+
    2023-06-14
  • 如何在 Go语言中使用日志包
    目录Go 语言标准库之log 包如何将日志消息存储在 Go 中的文件中定制你的日志记录器总结引言: 在我们的日常编程中,日志很重要。只要是我们写代码,就有可能出现 Bug。日志文件就...
    99+
    2022-11-13
  • Go语言使用goroutine及通道实现并发详解
    目录使用通道接收数据阻塞接收数据非阻塞接收数据接收任意数据,忽略掉接收的数据循环接收数据使用通道接收数据 在上一篇文章中介绍了通道以及使用通道发送数据,本篇接着了解通道的基本内容,如...
    99+
    2022-11-11
  • 你知道如何使用Go语言打包大量日志数据吗?
    Go语言是一种高效且易于使用的编程语言,它在开发过程中提供了许多强大的工具和特性。在大规模的应用程序中,日志记录是一项非常重要的任务。Go语言提供了许多标准库,可以方便地记录和处理日志数据。 在本文中,我们将介绍如何使用Go语言来打包大量的...
    99+
    2023-09-01
    大数据 日志 打包
  • Go语言中unsafe包怎么使用
    本文小编为大家详细介绍“Go语言中unsafe包怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Go语言中unsafe包怎么使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1. 什么是unsafe包un...
    99+
    2023-07-05
  • Go语言的sort包函数如何使用
    本篇内容主要讲解“Go语言的sort包函数如何使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go语言的sort包函数如何使用”吧!sort包简介官方文档Golang的sort包用来排序,二分...
    99+
    2023-06-30
  • Spring与Go语言:如何使用IDE打包?
    在开发中,我们经常需要将代码打包为可执行文件,以便在其他环境中运行。对于Java项目,我们通常使用Maven或Gradle来完成这项工作。而对于Go语言项目,我们则可以使用Go自带的工具来打包。那么,如何在IDE中使用这些工具呢?本文将为...
    99+
    2023-08-16
    spring 打包 ide
  • 如何使用Go语言打包和加载HTTP模块?
    Go语言是一种简单、高效的编程语言,它被广泛应用于各种领域,特别是在Web开发中。HTTP模块是开发Web应用程序中必不可少的一部分,Go语言提供了许多方便的工具来打包和加载HTTP模块。在本文中,我们将介绍如何使用Go语言打包和加载HTT...
    99+
    2023-10-18
    http 打包 load
  • 如何使用Go语言中的普通的函数
    本篇内容主要讲解“如何使用Go语言中的普通的函数”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何使用Go语言中的普通的函数”吧!普通函数在 Go 语言中普通函数的定义格式为 func [函数名...
    99+
    2023-06-15
  • Go语言中的闭包怎么使用
    这篇文章主要讲解了“Go语言中的闭包怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Go语言中的闭包怎么使用”吧!闭包基本介绍闭包就是 一个函数 和其相关的&nbs...
    99+
    2023-07-04
  • 浅析Go语言中闭包的使用
    目录闭包基本介绍闭包实现数字累加代码说明代码分析闭包案例上代码代码说明闭包基本介绍 闭包就是 一个函数 和其相关的 引用环境 组合的一个整体 ...
    99+
    2022-12-08
    Go语言闭包使用 Go语言闭包 Go 闭包
  • Go语言中的包Package怎么使用
    本文小编为大家详细介绍“Go语言中的包Package怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Go语言中的包Package怎么使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。问题一、Go使用Pac...
    99+
    2023-07-02
  • Go语言中goroutine和WaitGroup如何使用
    本篇内容主要讲解“Go语言中goroutine和WaitGroup如何使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go语言中goroutine和WaitGroup如何使用”吧!Go语言中g...
    99+
    2023-07-05
  • 怎么使用Go语言sync包与锁实现限制线程对变量的访问
    本篇内容主要讲解“怎么使用Go语言sync包与锁实现限制线程对变量的访问”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么使用Go语言sync包与锁实现限制线程对变量的访问”吧!Go语言中 sy...
    99+
    2023-07-06
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作