GO语言网络编程(并发编程)Channel

2023-11-15

GO语言网络编程(并发编程)Channel

1、Channel

1.1.1 Channel

单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。

虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。

Go语言的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。

如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。

Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。

1.1.2 channel类型

channel是一种类型,一种引用类型。声明通道类型的格式如下:

var 变量 chan 元素类型

举几个例子:

    var ch1 chan int   // 声明一个传递整型的通道
    var ch2 chan bool  // 声明一个传递布尔型的通道
    var ch3 chan []int // 声明一个传递int切片的通道

1.1.3. 创建channel

通道是引用类型,通道类型的空值是nil。

var ch chan int
fmt.Println(ch) // <nil>

声明的通道后需要使用make函数初始化之后才能使用。

创建channel的格式如下:

make(chan 元素类型, [缓冲大小])

channel的缓冲大小是可选的。

举几个例子:

ch4 := make(chan int)
ch5 := make(chan bool)
ch6 := make(chan []int)

1.1.4 channel操作

通道有发送(send)、接收(receive)和关闭(close)三种操作。

发送和接收都使用<-符号。

现在我们先使用以下语句定义一个通道:

ch := make(chan int)

发送
将一个值发送到通道中。

ch <- 10 // 把10发送到ch中

接收
从一个通道中接收值。

x := <- ch // 从ch中接收值并赋值给变量x
<-ch       // 从ch中接收值,忽略结果

关闭
我们通过调用内置的close函数来关闭通道。

close(ch)

关于关闭通道需要注意的事情是,只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。

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

    1.对一个关闭的通道再发送值就会导致panic2.对一个关闭的通道进行接收会一直获取值直到通道为空。
    3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
    4.关闭一个已经关闭的通道会导致panic

1.1.5 无缓冲的通道

在这里插入图片描述
无缓冲的通道又称为阻塞的通道。我们来看一下下面的代码:

func main() {
    ch := make(chan int)
    ch <- 10
    fmt.Println("发送成功")
}

上面这段代码能够通过编译,但是执行的时候会出现以下错误:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        .../src/github.com/pprof/studygo/day06/channel02/main.go:8 +0x54

为什么会出现deadlock错误呢?

因为我们使用ch := make(chan int)创建的是无缓冲的通道,无缓冲的通道只有在有人接收值的时候才能发送值。就像你住的小区没有快递柜和代收点,快递员给你打电话必须要把这个物品送到你的手中,简单来说就是无缓冲的通道必须有接收才能发送。

上面的代码会阻塞在ch <- 10这一行代码形成死锁,那如何解决这个问题呢?

一种方法是启用一个goroutine去接收值,例如:

func recv(c chan int) {
    ret := <-c
    fmt.Println("接收成功", ret)
}
func main() {
    ch := make(chan int)
    go recv(ch) // 启用goroutine从通道接收值
    ch <- 10
    fmt.Println("发送成功")
}

无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。

使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。

1.1.6 有缓冲的通道

解决上面问题的方法还有一种就是使用有缓冲区的通道。

在这里插入图片描述
我们可以在使用make函数初始化通道的时候为其指定通道的容量,例如:

func main() {
    ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道
    ch <- 10
    fmt.Println("发送成功")
}

只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的数量。就像你小区的快递柜只有那么个多格子,格子满了就装不下了,就阻塞了,等到别人取走一个快递员就能往里面放一个。

我们可以使用内置的len函数获取通道内元素的数量,使用cap函数获取通道的容量,虽然我们很少会这么做。

1.1.7

可以通过内置的close()函数关闭channel(如果你的管道不往里存值或者取值的时候一定记得关闭管道)

package main

import "fmt"

func main() {
    c := make(chan int)
    go func() {
        for i := 0; i < 5; i++ {
            c <- i
        }
        close(c)
    }()
    for {
        if data, ok := <-c; ok {
            fmt.Println(data)
        } else {
            break
        }
    }
    fmt.Println("main结束")
}

1.1.8 如何优雅的从通道循环取值

当通过通道发送有限的数据时,我们可以通过close函数关闭通道来告知从该通道接收值的goroutine停止等待。当通道被关闭时,往该通道发送值会引发panic,从该通道里接收的值一直都是类型零值。那如何判断一个通道是否被关闭了呢?

我们来看下面这个例子:

// channel 练习
func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    // 开启goroutine将0~100的数发送到ch1中
    go func() {
        for i := 0; i < 100; i++ {
            ch1 <- i
        }
        close(ch1)
    }()
    // 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
    go func() {
        for {
            i, ok := <-ch1 // 通道关闭后再取值ok=false
            if !ok {
                break
            }
            ch2 <- i * i
        }
        close(ch2)
    }()
    // 在主goroutine中从ch2中接收值打印
    for i := range ch2 { // 通道关闭后会退出for range循环
        fmt.Println(i)
    }
}

从上面的例子中我们看到有两种方式在接收值的时候判断通道是否被关闭,我们通常使用的是for range的方式。

1.1.9单向通道

有的时候我们会将通道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用通道都会对其进行限制,比如限制通道在函数中只能发送或只能接收。

Go语言中提供了单向通道来处理这种情况。例如,我们把上面的例子改造如下:

func counter(out chan<- int) {
    for i := 0; i < 100; i++ {
        out <- i
    }
    close(out)
}

func squarer(out chan<- int, in <-chan int) {
    for i := range in {
        out <- i * i
    }
    close(out)
}
func printer(in <-chan int) {
    for i := range in {
        fmt.Println(i)
    }
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go counter(ch1)
    go squarer(ch2, ch1)
    printer(ch2)
}

其中,

1.chan<- int是一个只能发送的通道,可以发送但是不能接收;
2.<-chan int是一个只能接收的通道,可以接收但是不能发送。

在函数传参及任何赋值操作中将双向通道转换为单向通道是可以的,但反过来是不可以的。

1.1.10 通道总结

channel常见的异常总结,如下图:

在这里插入图片描述
注意:关闭已经关闭的channel也会引发panic。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

GO语言网络编程(并发编程)Channel 的相关文章

  • 在 Go 中初始化嵌入结构

    我有以下内容struct其中包含一个net http Request type MyRequest struct http Request PathParams map string string 现在我想初始化匿名内部结构http Req
  • 如何将 Unicode 字符转换为简单形式? [复制]

    这个问题在这里已经有答案了 有没有一个Go库可以Sj str m作为输入和返回Sjostrom作为输出 您可以使用golang org x text unicode norm来处理这个问题 package main import fmt i
  • “go.tools”的权限被拒绝错误

    当我尝试安装 go 工具时 我的权限被拒绝 usr local go pkg tool linux amd64 cover 我可以接受 因为它是 usr local 目录及需求root使用权 但我的第一个疑问是为什么当我设置时它试图安装在这
  • Golang中按长度分割字符串

    有谁知道如何在 Golang 中按长度分割字符串 例如 每 3 个字符分割 helloworld 那么理想情况下它应该返回一个 hel low orl d 数组 或者 一个可能的解决方案是在每 3 个字符后附加一个换行符 所有的想法都非常感
  • 为什么 json.Unmarshal 返回映射而不是预期的结构?

    看看这个游乐场 http play golang org p dWku6SPqj5 http play golang org p dWku6SPqj5 基本上 我正在工作的图书馆收到了interface 作为参数 然后需要json Unma
  • 在 Gorilla Mux 中嵌套子路由器

    我一直在使用gorilla mux https github com gorilla mux满足我的路由需求 但我注意到一个问题 当我嵌套多个子路由器时它不起作用 这是示例 func main r mux NewRouter StrictS
  • 与通道相比,sync.WaitGroup 的优势是什么?

    我正在开发一个并发 Go 库 我偶然发现了 goroutine 之间两种不同的同步模式 其结果相似 等待组 https play golang org p ZYPLlcp16TZ package main import fmt sync t
  • 错误:标准包中非标准导入“gopkg.in/yaml.v2”

    我正在尝试从以下位置导入 go yamlhttps github com go yaml yaml https github com go yaml yaml 并且我发现了一个 Google 没有提供帮助的错误 I ran go get g
  • Golang 中的确定性 RSA 加密 - 如何在多次加密下为给定消息获得相同的结果

    对于下面的RSA加密代码 每次对同一条消息进行加密时 结果都会不同 我发现这是由于rand Reader in the rsa EncryptOAEP功能使其更加安全doc https pkg go dev crypto rsa Encry
  • 在复杂的文件夹结构中进行测试

    我正在 golang 中构建一个设计模式存储库 为了运行所有测试 我使用这个 bash 脚本 有用 bin bash go test creational abstract factory go go test creational bui
  • 关闭长度未知的通道

    当不了解频道时我无法关闭频道 length package main import fmt time func gen ch chan int var i int for time Sleep time Millisecond 10 ch
  • Facebook服务器端登录、CORS

    我正在实现一个带有 FB 服务器端登录的网站 简化步骤如下 一个简单的按钮触发 JS 脚本 该脚本调用我的后端 APIhttps localhost fblogin function sendFbLoginData get https lo
  • 使用泛型:类型参数 T 不能与 == 进行比较

    我正在操场上玩 Go Generics 尝试编写一些通用数组函数 https gotipplay golang org p vS7f Vxxy2j https gotipplay golang org p vS7f Vxxy2j packa
  • 指针上定义的方法仍然可以用值调用

    Effective Go 文档说明如下 关于接收者的指针与值的规则是 可以在指针和值上调用值方法 但只能在指针上调用指针方法 http tip golang org doc effective go html pointers vs val
  • 将中间件与 Golang Gorilla mux 子路由器结合使用

    如何将中间件应用到 Go 中大猩猩工具包 http www gorillatoolkit org 多路复用器子路由器 我有以下代码 router mux NewRouter StrictSlash true apiRouter router
  • 如何拥有在标准输出上更新的就地字符串

    我想输出到标准输出并让输出 覆盖 以前的输出 例如 如果我输出On 1 10 我想要下一个输出On 2 10覆盖On 1 10 我怎样才能做到这一点 stdout是一个流 io Writer 您无法修改已写入其中的内容 什么can更改的是该
  • 关于编写惯用的 Golang 的建议

    我正在掌握 Golang 的做事方式 首先是一些示例代码 package main import log os func logIt s string f os OpenFile errors log os O RDWR os O CREA
  • 所有可能的 GOOS 价值?

    如果我做对了 GOOS在编译源代码时确定 为了更好地支持多个操作系统 我感兴趣的是GOOS可能 当然 Go 是开源的 所以它可能有无限的可能性 所以我真正想要的是一个 通用列表 已知值为 windows linux darwin or fr
  • 对嵌套结构使用自定义解组时,GoLang 结构无法正确解组

    我们需要对嵌套在多个其他结构中的结构使用自定义解组器 而这些结构不需要自定义解组器 我们有很多类似的结构B下面定义的结构 类似于嵌套A 代码的输出是true false 0 预期的true false 2 有任何想法吗 Go 游乐场示例he
  • 共享 GOPATH 的良好做法是什么?

    我刚刚开始学习 Go 并阅读现有代码以了解 其他人是如何做的 在这样做时 go 工作空间 的使用 特别是当它与项目的依赖项相关时 似乎无处不在 在处理各种 Go 项目时 使用单个或多个 Go 工作区 即 GOPATH 的定义 的常见最佳实践

随机推荐

  • html获得url参数

    得到url搜索串 function getURLSearch isParent var strSearch if isParent strSearch window parent location search else strSearch
  • 工厂模式+策略模式的使用

    项目中会有这样的情形 通过某个参数进入不同的if分支 但是if分支太多就会导致项目难以维护 可读性也会降低 如下情形 public class Test public static void main String args String
  • git push提交报错文件过大,且去掉大文件也报同样的错误(转)

    错误原因 大文件存在没有被提交的commit记录里面 解决方案 删除有大文件的commit记录即可移除大文件的正确姿势 git rm cached giant file 文件名 Stage our giant file for remova
  • 三种常见的卷积概述(线性卷积周期卷积圆周卷积)

    note 2020 07 26搬运 下面的内容来自我的公众号 yhm同学 upd 2020 10 18 推荐学习辅助用书 江志红的 深入浅出数字信号处理 有别于学院派的教材 这本书讲得比较易懂清楚 upd 2020 10 18 数字信号处理
  • 【收藏向】一文弄懂什么是ERC20

    本文只做技术探讨 谨防数字加密货币炒作风险 Token Token 即通证 是以数字形式存在的权益凭证 它代表的是一种权利 一种固有和内在的价值 货币 积分 股票等权益证明 都可以由通证来代表 它代表着数字资产 下图就是在 opensea
  • 搜索引擎算法系列-BloomFilter算法解析及扩展算法

    通常存在下面的一些存在性检查方法 1 使用Set
  • QT--emit

    本文为学习记录 若有错误 请联系作者 谦虚受教 文章目录 前言 一 emit 二 相关代码 1 h文件 2 cpp文件 总结 前言 要努力 但是不要急 繁花锦簇 硕果累累都需要过程 一 emit emit是不同窗口 类间的触发信号 当对象改
  • 主线科技拿下数亿元新融资,自动驾驶卡车迎来拐点

    自图森未来敲响了自动驾驶公司上市的第一钟后 自动驾驶卡车赛道似乎迎来了非常不错的拐点 一批科技公司在产品落地 商业模式上也形成了独特的竞争优势 比如主线科技 其不仅率先实现了超百台港口无人驾驶集卡的交付 港口无人驾驶 去安全员 的常态化运营
  • 想用好虚幻4引擎做游戏,你需要避免这些扰人的坑(备忘)

    在手游品质越发上扬的如今 已经有不少厂商开始使用一些性能更好的引擎 去尝试游戏制作了 而虚幻4引擎 以下简称UE4 就是其中之一 在这款引擎中已经诞生了诸如 铁拳7 地狱之刃 帕拉贡 等一系列大作 对玩家而言 这些作品都是不折不扣的视觉盛宴
  • gitlab修改密码后无法pull的解决方法

    在登录gitlab的时候发现密码忘记了 在重新设置了密码之后 git无法pull代码 需要改windows下的凭据管理器中对应gitlab地址的凭据密码 把密码修改成你的新密码 个人建的交流群1125844267 欢迎大家加入 如果内容对大
  • OpenGL文字渲染的实例(C/C++)

    OpenGL文字渲染的实例 C C 在计算机图形学中 文字渲染是一个常见的任务 它涉及将字符或文本字符串呈现在屏幕上 OpenGL是一个广泛使用的图形库 它提供了强大的功能来渲染2D和3D图形 并且也支持文字渲染 本文将介绍如何使用Open
  • springboot 没有找到service_Spring Boot 应用程序五种部署方式

    翻译自 Deploying Spring Boot Applications 1 原作者 Murat Artim 2 可以使用各种方法将 Spring Boot 应用程序部署到生产系统中 在本文中 我们将通过以下 5 种方法来部署 Spri
  • 卡尔曼滤波之线性滤波,标量滤波

    卡尔曼滤波器由一系列递归数学公式描述 它们提供了一种高效可 计算的方法来估计过程的状态 并使估计均方误差最小 卡尔曼滤波 器应用广泛且功能强大 它可以估计信号的过去和当前状态 甚至能 估计将来的状态 即使并不知道模型的确切性质 对于Kalm
  • JAVA 快速排序算法(详细实现过程介绍)

    快速排序采用了一种分治的策略 通常称其为分治法 Divide and ConquerMethod 空间复杂度 快速排序是一种原地排序 只需要一个很小的栈作为辅助空间 空间复杂度为O log2n 所以适合在数据集比较大的时候使用 时间复杂度
  • 右腿驱动电路

    转自 http www 360doc com content 18 0312 15 1751130 736374982 shtml 1 使用目的 人和外界环境 尤其是电力线会形成容性耦合 这个耦合到的能量通过人体流入大地 而人体是带电阻的
  • 最全的目标检测与跟踪发展文献综述

    实时目标检测技术作为计算机视觉领域的重要研究方向之一 他在军事侦察 视频监控 智能战斗等领域有着重要的应用场景 随着深度学习的发展和硬件水平的提升 各种深度学习的算法对于航空影响的自动化起到了巨大的推动作用 基于深度神经网络的视觉算法通过自
  • 装饰者模式---装饰者模式和桥接模式的区别

    装饰者模式 什么是装饰者模式 动态将职责附加到对象上 若要扩展功能 装饰者提供了比继承更具弹性的代替方案 装饰者模式又称为包装模式 它主要是为了扩展对象的功能 包装类通过持有对象的引用 将对象传到包装类里面 聚合 把对象包装起来 可以在调用
  • 【数学建模】灰度预测之关联度求解

    灰度预测适用范围 在实际中 若得到的是离散的 规律性不强的数据 此时线性回归就不适用了 我们需要采用灰度预测的方法 灰度预测法则是一种对含有不确定因素的系统进行预测的方法 白色系统 黑色系统 灰色系统 白色系统 指一个系统的内部特征是完全已
  • TP6关于hasOne的用法

    TP6关于hasOne的用法 实际操作中看如下案例 文章分类表 CREATE TABLE case category id bigint 20 unsigned NOT NULL AUTO INCREMENT COMMENT 主键id na
  • GO语言网络编程(并发编程)Channel

    GO语言网络编程 并发编程 Channel 1 Channel 1 1 1 Channel 单纯地将函数并发执行是没有意义的 函数与函数间需要交换数据才能体现并发执行函数的意义 虽然可以使用共享内存进行数据交换 但是共享内存在不同的goro