14 go语言(golang) - 并发编程goroutine和channel

前言

并发编程是指在计算机程序中同时执行多个计算任务的技术。这种编程方式旨在利用多核处理器的计算能力,提高程序的执行效率和响应速度。而Go 语言的并发编程主要依赖于两个核心概念:Goroutine 和 Channel。

Goroutine

Goroutine 是 Go 语言中的一种轻量级线程管理机制。它是 Go 并发编程的核心特性之一,允许开发者以非常简单和高效的方式实现并发操作。

1、特点

  1. 轻量级:与传统的操作系统线程相比,goroutine 非常轻量。一个典型的 goroutine 只占用几 KB 的内存,而不是 MB 级别。这使得在同一时间可以运行成千上万个 goroutine,而不会对系统资源造成太大压力。

  2. 调度管理:Go 运行时包含了自己的调度器,用于管理 goroutines 的执行。这个调度器会将多个 goroutines 映射到少数几个 OS 线程上,从而有效地利用多核处理器。

  3. 简单启动:启动一个新的 goroutine 非常简单,只需要在函数调用前加上 go 关键字即可。

  4. 自动栈增长:goroutines 使用的是可增长的栈,这意味着它们开始时使用很小的内存,并根据需要动态扩展。这种特性进一步提高了其效率和灵活性。

  5. 与 Channel 配合使用:虽然 goroutines 可以独立工作,但通常会与 Channels 一起使用,以便在不同的 goroutines 间进行通信和同步。

2、示例

package mainimport ("fmt""testing"
)func Test1(t *testing.T) {for i := 0; i < 10; i++ {go fmt.Printf("go线程 %d \n", i)}fmt.Println("主流程")
}

输出:

=== RUN   Test1
主流程
go线程 2 
go线程 3 
go线程 5 
--- PASS: Test1 (0.00s)
go线程 0 
go线程 7 
go线程 6 
go线程 8 
PASS
go线程 4 

多执行几次发现会少了一些打印,如 1和9线程

改进:

func Test2(t *testing.T) {for i := 0; i < 10; i++ {go fmt.Printf("go线程 %d \n", i)}time.Sleep(1 * time.Second)fmt.Println("主流程")
}

输出:

=== RUN   Test2
go线程 9 
go线程 0 
go线程 1 
go线程 2 
go线程 3 
go线程 4 
go线程 7 
go线程 8 
go线程 5 
go线程 6 
主流程
--- PASS: Test2 (1.00s)
PASS

3、waitGroup

考虑如何优雅的等待子线程执行完成,而不是简单的让主线程等待,常用的方法是使用 sync.WaitGroupWaitGroup 提供了一种简单而有效的方式来等待一组 goroutines 完成它们的工作,而不需要显式地让主线程休眠。它通过一个计数器来跟踪 goroutines 的数量。你可以增加或减少这个计数器,并且可以阻塞直到计数器变为零,这表示所有被跟踪的 goroutines 都已完成。

基本步骤

  1. 创建 WaitGroup 实例:在你的程序中创建一个 WaitGroup 实例。
  2. 增加计数:每启动一个新的 goroutine,就调用 Add(1) 来增加 WaitGroup 的计数。
  3. 标记完成:在每个 goroutine 内部,任务完成时调用 Done() 来减少 WaitGroup 的计数。
  4. 等待所有任务完成:在主线程中调用 Wait() 方法,它会阻塞直到所有被追踪的任务都标记为完成。
// 模拟工作的函数
func exec(num int, wg *sync.WaitGroup) {defer wg.Done() // 确保工作结束后通知 WaitGroupgo fmt.Printf("go线程 %d \n", num)
}func Test3(t *testing.T) {group := sync.WaitGroup{}// 启动多个goroutine并使用WaitGroup追踪它们for i := 0; i < 10; i++ {group.Add(1) // 增加WaitGroup计数go exec(i, &group)}group.Wait() // // 阻塞主线程,直到所有线程都执行完毕fmt.Println("主流程")
}

注意事项

  • 确保 Done 调用:始终确保每个启动的 goroutine 在其逻辑结束时调用了 Done() 方法。这通常通过使用关键字 defer 来实现,以确保即使发生错误也能正确递减。
  • 避免重复 Add 和 Done 操作:不要对同一组操作多次进行 Add 或 Done 调用,否则可能导致不一致状态和死锁。

Channel

在 Go 语言中,Channel 是一种用于 goroutine 之间进行通信的机制。它可以让一个 goroutine 发送特定类型的值到 Channel 中,然后另一个 goroutine 从该 Channel 接收值,从而实现数据的安全传递和同步。

1、基本特性

  1. 类型化:Channel 是类型化的,这意味着你需要指定要传输的数据类型。
  2. 阻塞行为
    • 发送阻塞:当一个 goroutine 向 Channel 发送数据时,如果没有其他 goroutine 正在等待接收这个数据,那么发送操作会被阻塞,直到有接收者。
    • 接收阻塞:同样地,当一个 goroutine 尝试从 Channel 接收数据时,如果没有其他 goroutine 正在向这个 Channel 发送数据,那么接收操作会被阻塞,直到有新的数据可用。
  3. 方向性:Channel 可以是双向的,也可以是单向(只读或只写)的。

2、创建和使用 Channel

func Test4(t *testing.T) {ch := make(chan int)for i := 0; i < 10; i++ {go send(i, ch)}for i := 0; i < 10; i++ {value := <-chfmt.Printf("接受到消息:%d\n", value)time.Sleep(time.Second)}
}func send(i int, ch chan int) {fmt.Printf("-- 准备发送:%d\n", i)ch <- ifmt.Printf("-- 发送成功!%d\n", i) // 这行打印会被阻塞
}

输出:

=== RUN   Test4
-- 准备发送:9
-- 发送成功!9
接受到消息:9
-- 准备发送:2
-- 准备发送:6
-- 准备发送:1
-- 准备发送:8
-- 准备发送:3
-- 准备发送:4
-- 准备发送:5
-- 准备发送:0
-- 准备发送:7
接受到消息:2
-- 发送成功!2
接受到消息:6
-- 发送成功!6
接受到消息:1
-- 发送成功!1
接受到消息:8
-- 发送成功!8
接受到消息:3
-- 发送成功!3
接受到消息:4
-- 发送成功!4
接受到消息:5
-- 发送成功!5
接受到消息:0
-- 发送成功!0
接受到消息:7
-- 发送成功!7
--- PASS: Test4 (10.01s)
PASS

3、带缓冲区的 Channel

带缓冲区的 Channels 可以存储一定数量的数据,而不会立即导致 sender 或 receiver 被阻塞。创建带缓冲区 Channels 时,可以指定其容量。

func Test5(t *testing.T) {ch := make(chan int, 5)for i := 0; i < 10; i++ {go send(i, ch)}for i := 0; i < 10; i++ {value := <-chfmt.Printf("接受到消息:%d\n", value)time.Sleep(time.Second)}
}

输出:

=== RUN   Test5
-- 准备发送:9
-- 发送成功!9
接受到消息:9
-- 准备发送:0
-- 发送成功!0
-- 准备发送:1
-- 发送成功!1
-- 准备发送:2
-- 发送成功!2
-- 准备发送:3
-- 发送成功!3
-- 准备发送:7
-- 发送成功!7
-- 准备发送:8
-- 准备发送:6
-- 准备发送:4
-- 准备发送:5
接受到消息:0
-- 发送成功!8
接受到消息:1
-- 发送成功!6
接受到消息:2
-- 发送成功!4
接受到消息:3
-- 发送成功!5
接受到消息:7
接受到消息:8
接受到消息:6
接受到消息:4
接受到消息:5
--- PASS: Test5 (10.01s)
PASS

长度(Length)

  • 定义:Channel 的长度是指当前 Channel 中已存储的数据元素的数量。
  • 获取方式:可以使用内置函数 len(ch) 来获取 Channel 的当前长度。

容量(Capacity)

  • 定义:Channel 的容量是指它最多可以容纳多少个数据元素。对于无缓冲的 channel,容量为零;对于带缓冲的 channel,容量是在创建时指定的。
  • 获取方式:可以使用内置函数 cap(ch) 来获取 Channel 的总容量。
func TestChannelCapAndLen(t *testing.T) {ch := make(chan string, 3)ch <- "a"ch <- "b"fmt.Println("容量为:", cap(ch))fmt.Println("长度为:", len(ch))fmt.Println("读取一个元素:", <-ch)fmt.Println("新的长度为:", len(ch))
}

输出:

=== RUN   TestChannelCapAndLen
容量为: 3
长度为: 2
读取一个元素: a
新的长度为: 1
--- PASS: Test9 (0.00s)
PASS

4、单向 Channel

单向 Channel 用于限制函数对 channel 的访问权限,只能用于 send 或 receive 操作。这种限制可以帮助避免错误地使用 channel。

func sendOnly(i int, ch chan<- int) {fmt.Printf("-- 准备发送:%d\n", i)ch <- ifmt.Printf("-- 发送成功!%d\n", i)
}func receiveOnly(ch <-chan int) {value := <-chfmt.Printf("接受到消息:%d\n", value)time.Sleep(time.Second)
}func Test6(t *testing.T) {ch := make(chan int)for i := 0; i < 10; i++ {go sendOnly(i, ch)}for i := 0; i < 10; i++ {receiveOnly(ch)}
}

5、关闭Channel

如果channel没有关闭,消费者仍然在等待数据,则可能导致死锁

func Test7(t *testing.T) {ch := make(chan int)for i := 0; i < 10; i++ {go sendOnly(i, ch)}// 消费者仍然在等待数据,则可能导致死锁for value := range ch {fmt.Printf("接受到消息:%d\n", value)time.Sleep(time.Second)}
}

报错:

fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:
testing.(*T).Run(0xc00010c4e0, {0x3d35a17?, 0x125836780012db50?}, 0x3da35f0)/Users/fangyirui/sdk/go1.23.2/src/testing/testing.go:1751 +0x3ab
testing.runTests.func1(0xc00010c4e0)/Users/fangyirui/sdk/go1.23.2/src/testing/testing.go:2168 +0x37
testing.tRunner(0xc00010c4e0, 0xc00012dc70)/Users/fangyirui/sdk/go1.23.2/src/testing/testing.go:1690 +0xf4
testing.runTests(0xc000010030, {0x3e7cbe0, 0x9, 0x9}, {0x3c86030?, 0x3c85c9a?, 0x0?})/Users/fangyirui/sdk/go1.23.2/src/testing/testing.go:2166 +0x43d
testing.(*M).Run(0xc00007a0a0)/Users/fangyirui/sdk/go1.23.2/src/testing/testing.go:2034 +0x64a
main.main()_testmain.go:61 +0x9bgoroutine 5 [chan receive]:
awesomeProject/_28goroutine.Test7(0xc00010c680?)/Users/fangyirui/GolandProjects/awesomeProject/_28goroutine/1_test.go:114 +0x105
testing.tRunner(0xc00010c680, 0x3da35f0)/Users/fangyirui/sdk/go1.23.2/src/testing/testing.go:1690 +0xf4
created by testing.(*T).Run in goroutine 1/Users/fangyirui/sdk/go1.23.2/src/testing/testing.go:1743 +0x390

关闭 Channel 是一个重要的操作,它通知接收者不会再有新的数据发送到这个 Channel 上。

  1. 通知完成:通过关闭一个 Channel,可以向接收者表明没有更多的数据会被发送。这对于需要知道何时停止读取数据的消费者来说非常有用。
  2. 避免死锁:如果所有发送者都退出了,而消费者仍然在等待数据,则可能导致死锁。通过显式地关闭 Channel,可以避免这种情况。

改进:使用内置函数 close 来关闭一个 channel

func sendOnlyV2(i int, ch chan<- int, wg *sync.WaitGroup) {fmt.Printf("-- 准备发送:%d\n", i)ch <- ifmt.Printf("-- 发送成功!%d\n", i)wg.Done()
}func Test8(t *testing.T) {wg := sync.WaitGroup{}ch := make(chan int)for i := 0; i < 10; i++ {wg.Add(1)go sendOnlyV2(i, ch, &wg)}// 启动另一个goroutine来等待所有send操作完成后再关闭channel,不然channel关闭后还没发送完数据会报错go func() {wg.Wait()// 发送完毕,关闭channelclose(ch)fmt.Println("channel 已经关闭")}()for value := range ch {fmt.Printf("接受到消息:%d\n", value)time.Sleep(time.Second)}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/476814.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用Jest测试你的React组件

在本文中&#xff0c;我们将了解如何使用Jest&#xff08;Facebook 维护的一个测试框架&#xff09;来测试我们的React组件。我们将首先了解如何在纯 JavaScript 函数上使用 Jest&#xff0c;然后再了解它提供的一些开箱即用的功能&#xff0c;这些功能专门用于使测试 React 应…

Stm32f103X HAL库 串口DMA空闲中断学习 踩坑记

捡 了一个Stm32f103X的工控板, 开发工具是 STM32cubeide ,复制了之前闲的时候建的一个485通讯的空工程&#xff0c;只配置了圈中的引脚,用的是usart3 ,增加了需要用的io,编程器是网上几块钱买的jlink-ob,带模拟串口的. 在学习串口通信的过程中总感觉不太顺利 ,刚开始用串口中断…

云服务器部署WebSocket项目

WebSocket是一种在单个TCP连接上进行全双工通信的协议&#xff0c;其设计的目的是在Web浏览器和Web服务器之间进行实时通信&#xff08;实时Web&#xff09; WebSocket协议的优点包括&#xff1a; 1. 更高效的网络利用率&#xff1a;与HTTP相比&#xff0c;WebSocket的握手只…

PDF内容提取,MinerU使用

准备环境 # python 3.10 python3 -m pip install huggingface_hub python3 -m pip install modelscope python3 -m pip install -U magic-pdf[full] --extra-index-url https://wheels.myhloli.com下载需要的模型 import json import osimport requests from huggingface_hub…

掌握 Spring 事务管理:深入理解 @Transactional 注解

在业务方法上使用Transactional开启声明式事务时&#xff0c;很有可能由于使用方式有误&#xff0c;导致事务没有生效。 环境准备 表结构 CREATE TABLE admin (id bigint(20) unsigned NOT NULL AUTO_INCREMENT,username varchar(255) DEFAULT NULL,password varchar(255) …

设计模式之 观察者模式

观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听一个主题对象&#xff08;Subject&#xff09;。当主题对象的状态发生变化时&#xff0c;所有依赖于它的观察者都会得到…

【python】将word文档内容转换为excel表格

在日常工作中&#xff0c;我们经常需要将Word文档中的内容提取并转换为Excel表格&#xff0c;以便进行数据分析和处理。本文将介绍如何使用Python编写一个简单的程序&#xff0c;将Word文档中的内容转换为Excel表格。 一.实例 使用以下word文档作为例子&#xff1a; 工具界面如…

Linux|进程程序替换

目录 什么是进程替换 替换原理 exec函数 exec* 函数的共性 什么是进程替换 进程程序替换是指将一个进程中正在运行的程序替换为另一个全新的程序的过程&#xff0c;但替换不是创建新进程&#xff0c;只是将对应程序的代码和数据进行替换。具体来说&#xff0c;这个替换过程涉…

大数运算(加减乘除和输入、输出模块)

为什么会有大数呢&#xff1f;因为long long通常为64位范围约为 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807&#xff0c;最多也就19位&#xff0c;那么超过19位的如何计算呢&#xff1f;这就引申出来大数了。 本博客适合思考过这道题&#xff0c;但是没做出来或…

IntelliJ+SpringBoot项目实战(四)--快速上手数据库开发

对于新手学习SpringBoot开发&#xff0c;可能最急迫的事情就是尽快掌握数据库的开发。目前数据库开发主要流行使用Mybatis和Mybatis Plus,不过这2个框架对于新手而言需要一定的时间掌握&#xff0c;如果快速上手数据库开发&#xff0c;可以先按照本文介绍的方式使用JdbcTemplat…

flex布局 昵图网【案例】

效果展示 只是个大概&#xff0c;可自己完善。 昵图网 代码展示 <body><!-- https://static.ntimg.cn/original/images/soso.png --><div class"container"><div class"header"><!-- <div class"logo"><i…

[第五空间 2021]pklovecloud 详细题解

知识点: 构造POP链 PHP类的作用域 NULL强比较 目录穿越 源码如下: <?php include flag.php; class pkshow { function echo_name() { return "Pk very safe^.^"; } } class acp { protected $cinder; public $neutron;public $n…

dockerfile构建Nginx镜像练习二(5-2)

环境准备&#xff1a; (1)保证拥有centos基础镜像 docker images | grep centos (2)服务器保证可以连接外网 1.创建工作目录 mkdir nginx cd nginx 2.在工作目录中创建并编写Dockerfile文件 vim dockerfile #定义基础镜像 FROM centos:7#维护者信息(可缺省) MAINTAINER d…

Android Surfaceflinger显示图层合成方式

Android SurfaceFlinger是Android系统中负责窗口管理和图像合成的核心组件。它接收来自不同应用的图层数据&#xff0c;并将这些图层合并成一个单一的图像&#xff0c;然后输出到显示设备上。SurfaceFlinger的合成方式主要涉及两种&#xff1a;Client合成和Device合成。 adb s…

wsl安装

一. wsl简介 1. wsl和wsl2的区别 wsl需要把linux命令翻译为windows命令&#xff0c;性能差一些。 wsl2直接使用linux内核&#xff0c;不需要翻译&#xff0c;性能好&#xff0c;但开销相对大一点&#xff0c;因为需要多运行一个hyper-v虚拟机 (并非完整的虚拟机&#xff0c;是…

任务通知的本质(任务通知车辆运行) 软件定时器的本质(增加游戏音效)

任务通知的本质 没有任务通知 所谓"任务通知"&#xff0c;你可以反过来读"通知任务"。 我们使用队列、信号量、事件组等等方法时&#xff0c;并不知道对方是谁。使用任务通知时&#xff0c;可 以明确指定&#xff1a;通知哪个任务。 使用队列、信号量、…

Kubernetes的pod控制器

文章目录 一&#xff0c;什么是pod控制器二&#xff0c;pod控制器类型&#xff08;重点&#xff09;1.ReplicaSet2.Deployment3.DaemonSet4.StatefulSet5.Job6.Cronjob 三&#xff0c;pod与控制器的关系1.Deployment2.SatefulSet2.1StatefulSet组成2.2headless的由来2.3有状态服…

【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录

背景 Jetbrain IDE 支持生成 Test 类&#xff0c;其中选择JUnit5 和 JUnit&#xff0c;但是感觉这不是标准的单元测试&#xff0c;因为接口命名吧。 差异对比 两者生成的单测API名称同原API&#xff0c;没加test前缀的。使用差异主要表现在&#xff1a; setUp &#xff06; …

知识中台在多语言客户中的应用

在全球化的商业环境中&#xff0c;企业面临着多语言客户服务的挑战。HelpLook知识中台作为一种智能化解决方案&#xff0c;为企业提供了一个强大的工具&#xff0c;以实现多语言客户服务的自动化和优化。 一、多语言客户服务的重要性 多语言客户服务对于跨国企业至关重要&…

使用 Elastic AI Assistant for Search 和 Azure OpenAI 实现从 0 到 60 的转变

作者&#xff1a;来自 Elastic Greg Crist Elasticsearch 推出了一项新功能&#xff1a;Elastic AI Assistant for Search。你可以将其视为 Elasticsearch 和 Kibana 开发人员的内置指南&#xff0c;旨在回答问题、引导你了解功能并让你的生活更轻松。在 Microsoft AI Services…