Go协程及并发锁应用指南

概念

协程(Goroutine)是Go语言独有的并发体,是一种轻量级的线程,也被称为用户态线程。相对于传统的多线程编程,协程的优点在于更加轻量级,占用系统资源更少,切换上下文的速度更快,不需要像多线程编程一样处理锁等线程安全问题。
在这里插入图片描述

1. 协程的创建

在Go语言中,可以使用go语句来启动一个协程。go语句后面跟的是一个函数调用,即启动一个新协程去执行该函数。例如:

func main() {go printHello() // 启动一个goroutine去执行printHello函数fmt.Println("main function")
}func printHello() {fmt.Println("hello goroutine")
}

在上面的代码中,printHello函数会在新的协程中执行,而不会阻塞主线程。

一、协程间的通信

在这里插入图片描述

1. Channel的定义和初始化

在Go语言中,协程之间的通信主要通过channel实现。使用make函数可以创建一个channel,其语法为:

channel_name := make(chan data_type)

其中,data_type为channel中传输数据的类型。例如,创建一个传输int类型数据的channel:

ch := make(chan int)
2. Channel的读写

Channel既可以进行发送操作,也可以进行接收操作。发送操作和接收操作都是阻塞的。

  • 发送操作:channel_name <- value
  • 接收操作:value := <-channel_name

二、并发锁的应用

在并发编程中,为了防止多个协程同时访问共享资源导致的数据竞争和不一致问题,需要使用锁机制来同步访问。Go语言提供了多种锁机制,包括互斥锁(Mutex)和读写锁(RWMutex)等。
在这里插入图片描述

1. 互斥锁(Mutex)

互斥锁是Go语言中最常用的锁机制,它确保同一时刻只有一个协程可以访问临界区。使用sync包中的Mutex类型来创建互斥锁,并通过Lock()Unlock()方法来加锁和解锁。

import "sync"var mutex = &sync.Mutex{}func criticalSection() {mutex.Lock()defer mutex.Unlock()// 临界区代码
}
2. 读写锁(RWMutex)

读写锁允许多个协程同时读取共享资源,但在有写操作时,需要互斥访问。使用sync包中的RWMutex类型来创建读写锁,通过RLock()RUnlock()Lock()Unlock()方法来分别获取读锁、释放读锁、获取写锁和释放写锁。

import "sync"var rwlock = &sync.RWMutex{}func read() {rwlock.RLock()defer rwlock.RUnlock()// 读取共享资源
}func write() {rwlock.Lock()defer rwlock.Unlock()// 修改共享资源
}

三、注意事项

  1. 锁的粒度:应尽可能减少锁的持有时间,避免降低程序的性能。
  2. 避免死锁:在使用锁时,需要仔细设计锁的使用顺序,避免循环等待导致的死锁。
  3. 选择合适的锁:根据具体场景选择合适的锁机制,如读写锁在读多写少的场景下可以显著提高性能。

在这里插入图片描述

四、简单完整示例

当然,以下是一个包含Go协程、Channel以及互斥锁(Mutex)完整示例的程序。这个程序将演示如何使用协程来处理并发任务,并通过Channel进行协程间的通信,同时使用互斥锁来保护共享资源的数据一致性。

package mainimport ("fmt""sync""time"
)// 假设有一个共享资源,我们需要保护它不被并发访问破坏
var (sharedResource intmutex          sync.Mutex
)// 一个用于修改共享资源的函数,通过互斥锁保证数据一致性
func modifySharedResource(increment int) {mutex.Lock()defer mutex.Unlock()sharedResource += incrementfmt.Printf("Modified sharedResource to %d\n", sharedResource)
}// 一个使用协程的函数,它会多次修改共享资源并通过Channel发送完成信号
func worker(done chan bool, increment int) {for i := 0; i < 5; i++ {modifySharedResource(increment)time.Sleep(time.Millisecond * 100) // 模拟耗时操作}done <- true // 发送完成信号
}func main() {done := make(chan bool, 2) // 创建一个带有缓冲的Channel,确保可以发送两个值而不会阻塞// 启动两个协程,每个协程都将尝试修改共享资源go worker(done, 10)go worker(done, 20)// 等待两个协程完成for i := 0; i < 2; i++ {<-done // 阻塞等待接收来自协程的完成信号}fmt.Println("All workers done. Final sharedResource:", sharedResource)
}

在这个示例中:

  1. 定义了一个全局的共享资源sharedResource和一个互斥锁mutex
  2. modifySharedResource函数用于修改共享资源,它首先加锁,修改完成后释放锁。
  3. worker函数是并发执行的函数,它接受一个done Channel和一个increment值作为参数。它会循环5次,每次循环都调用modifySharedResource来修改共享资源,并在每次修改后短暂睡眠以模拟耗时操作。循环结束后,它通过done Channel发送一个完成信号。
  4. main函数中,我们创建了一个done Channel和两个协程,每个协程以不同的increment值调用worker函数。
  5. 我们通过for循环和<-donedone Channel接收两次完成信号,确保两个协程都已完成工作。
  6. 最后,我们打印出最终的sharedResource值。

五、模拟错误收集示例

在Go语言中,协程(Goroutine)的错误处理通常是通过返回值、通道(Channel)或上下文(Context)来完成的。由于协程本质上是轻量级的线程,它们并不直接支持像传统线程那样的异常机制。不过,我们可以通过上述几种方式来实现错误传播和处理。

下面是一个扩展了之前示例的程序,其中增加了协程的错误处理。我们将使用通道(Channel)来传递错误和完成信号。

package mainimport ("errors""fmt""sync""time"
)// 假设的共享资源
var sharedResource int
var mutex sync.Mutex// 模拟可能失败的操作
func modifySharedResource(increment int) error {mutex.Lock()defer mutex.Unlock()// 假设某种条件下操作失败if increment < 0 {return errors.New("negative increment is not allowed")}sharedResource += incrementfmt.Printf("Modified sharedResource to %d\n", sharedResource)return nil
}// worker 协程函数,现在可以返回错误
func worker(done chan bool, errChan chan error, increment int) {for i := 0; i < 5; i++ {if err := modifySharedResource(increment); err != nil {errChan <- err // 将错误发送到错误通道return}time.Sleep(time.Millisecond * 100) // 模拟耗时操作}done <- true // 发送完成信号
}func main() {done := make(chan bool, 2)errChan := make(chan error, 2) // 创建一个用于传递错误的通道// 启动两个协程,一个可能遇到错误go worker(done, errChan, 10)go worker(done, errChan, -5)  // 这个将尝试一个负增量,应该失败// 等待协程完成或接收错误for i := 0; i < 2; i++ {select {case <-done:fmt.Println("Worker done")case err := <-errChan:fmt.Println("Error received:", err)// 根据需要处理错误,比如退出程序、记录日志等// 这里我们简单地打印错误并继续等待其他协程}}// 注意:在实际情况中,你可能需要一种方式来确保所有协程都已完成或都已报告错误// 这里为了简化,我们没有实现这样的逻辑fmt.Println("All workers potentially done or errors reported. Final sharedResource:", sharedResource)// 注意:如果某些协程可能永远不发送完成信号(比如因为死循环或无限等待),// 你可能需要一个超时机制或一种方式来优雅地关闭这些协程。
}

在这个示例中:

  • modifySharedResource 函数现在可以返回一个错误,如果增量是负数。
  • worker 函数现在接受一个额外的 errChan 通道,用于在发生错误时发送错误消息。如果发生错误,worker 函数将发送错误并立即返回。
  • main 函数中,我们使用了一个 select 语句来同时等待完成信号和错误。这允许我们在任何一个事件发生时都能立即响应。

请注意:这个示例并没有实现一个完美的错误处理策略,特别是如果某些协程可能永远不发送完成信号(比如因为死循环或无限等待)。在实际应用中,你可能需要实现更复杂的逻辑来确保程序的健壮性和可靠性。例如,你可以使用上下文(Context)来优雅地关闭或取消长时间运行的协程。

六、举一个实际应用的示例[更新购物车数量]

  1. 高效并发处理:购物车系统在高并发访问时,需要快速处理多个用户的请求。Go协程的轻量级特性使得能够轻松创建成千上万的并发任务,而不会对系统资源造成太大压力。

  2. 简化并发编程:Go的协程和通道(Channel)模型简化了并发编程的复杂度。通过通道进行协程间的通信,可以避免传统并发编程中的许多常见问题,如竞态条件、死锁等。

  3. 内置并发原语:Go标准库提供了丰富的并发原语,如互斥锁(Mutex)、读写锁(RWMutex)、条件变量(Cond)等,这些工具可以进一步帮助开发者编写安全、高效的并发代码。

  4. 易于调试和监控:Go的协程模型使得并发程序的调试和监控相对容易。通过工具如pproftrace等,开发者可以很方便地分析并发程序的性能瓶颈和潜在问题。

购物车并发处理完整示例

下面是一个简化的购物车并发处理示例,展示了如何使用Go协程和通道来处理多个用户的购物车更新请求。

package mainimport ("fmt""sync""time"
)// 假设的购物车结构
type ShoppingCart struct {Items map[string]intmutex sync.Mutex
}// 更新购物车的函数
func (c *ShoppingCart) Update(itemID string, quantity int) {c.mutex.Lock()defer c.mutex.Unlock()c.Items[itemID] += quantityfmt.Printf("Updated cart, item %s now has %d\n", itemID, c.Items[itemID])
}// 模拟处理购物车更新请求的协程
func handleCartUpdate(cart *ShoppingCart, itemID string, quantity int, done chan<- bool) {time.Sleep(time.Millisecond * 100) // 模拟处理时间cart.Update(itemID, quantity)done <- true // 发送完成信号
}func main() {cart := &ShoppingCart{Items: make(map[string]int)}done := make(chan bool, 10) // 假设最多同时处理10个请求// 模拟多个用户同时更新购物车for i := 0; i < 10; i++ {go handleCartUpdate(cart, fmt.Sprintf("item%d", i), 5, done)}// 等待所有更新完成for i := 0; i < 10; i++ {<-done}fmt.Println("All cart updates completed.")// 在这里可以进一步处理更新后的购物车,如发送到数据库保存等
}

在这个示例中:

  • 我们定义了一个ShoppingCart结构体,它包含一个商品项的映射和一个互斥锁来保护这个映射的并发访问。
  • Update方法用于更新购物车中的商品数量,它通过互斥锁来确保线程安全。
  • handleCartUpdate协程函数模拟了处理购物车更新请求的过程,它接收购物车、商品ID、数量和完成信号通道作为参数,并在更新完成后发送完成信号。
  • main函数中,我们创建了10个协程来模拟多个用户同时更新购物车的情况,并通过done通道等待所有更新完成。

请注意:这个示例为了简化而省略了一些重要的错误处理和优化措施,比如在实际应用中你可能需要处理网络请求、数据库操作等可能失败的情况,并且可能需要使用更复杂的并发控制策略来优化性能。

总结

通过以上指南,您可以更好地理解和应用Go协程及并发锁,在Go语言的并发编程中编写出高效、安全的代码。以上都是些简单代码片段示例,不能作为实际应用使用哦。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

如何快速清理Docker中的停止容器?

如何快速清理Docker中的停止容器? 方法一:使用`docker container prune`方法二:结合`docker ps`和`docker rm`注意(这些命令慎用,确定容器不需要之后再执行)💖The Begin💖点点关注,收藏不迷路💖 Docker容器在停止后可能会占用不必要的磁盘空间。如何清理这些停止的…

linux 双网卡服务器突然断电后网卡单通故障解决

某台linux 双网卡服务器突然断电后网卡单通故障解决 故障现象&#xff1a;断电后重启服务器&#xff0c;主用网卡IP只能同网段访问&#xff0c;其他网段无法访问&#xff0c;备用网卡则正常&#xff1b; 解决方案&#xff1a;route -n查询路由信息&#xff0c;发现主网卡路由…

el-table的树形结构结合多选框使用,实现单选父子联动,全选,反选功能

<template><div><el-table:data"tableData":row-key"rowKey":default-expand-all"defaultExpandAll":tree-props"treeProps"><!-- 开启树形多选 --><el-table-column v-if"showSelection" width…

【视频教程】基于python深度学习遥感影像地物分类与目标识别、分割实践技术应用

我国高分辨率对地观测系统重大专项已全面启动&#xff0c;高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成&#xff0c;将成为保障国家安全的基础性和战略性资源。未来10年全球每天获取的观测数据将超过10PB&#xff0c;遥感大数据时…

【前端】main.js中app.vue中 render函数的作用及使用背景

vue.js中的main.js中的作用是将app组件挂载到页面中&#xff0c;其中app组件是汇总所有组件元素的组件。main.js的创建vue实例。 #认为的版本 import APP from ./App.vue;new Vue({el:#root,template:<App></App>,components:{App}, })#实际的版本 /* 整个项目的入…

el-table表格的展开行,初始化的时候展开哪一行+设置点击行可展开功能

效果&#xff1a; 表格展开行官网使用&#xff1a; 通过设置 type"expand" 和 Scoped slot 可以开启展开行功能&#xff0c;el-table-column 的模板会被渲染成为展开行的内容&#xff0c;展开行可访问的属性与使用自定义列模板时的 Scoped slot 相同。 但是这种方法…

Linux环境基础开发工具---vim

1.快速的介绍一下vim vim是一款多模式的编辑器&#xff0c;里面有很多子命令&#xff0c;来实现代码编写操作。 2.vim的模式 vim一共有三种模式&#xff1a;底行模式&#xff0c;命令模式&#xff0c;插入模式。 2.1vim模式之间的切换 2.2 谈论常见的模式---命令模式&#xf…

Linux 35.5 + JetPack v5.1.3@CUDA安装和版本切换

Linux 35.5 JetPack v5.1.3CUDA安装和版本切换 1. 源由2. 现象3. 分析3.1 看本质3.2 善动脑3.3 笔记回忆3.4 底层思考3.5 多版本 4. 版本切换5. 总结 1. 源由 最近遇到一些CUDA编程&#xff0c;以及编译链接过程出现一些版本不匹配的问题。 首先&#xff0c;申明下&#xff…

No module named MYSQLdb 问题解决

问题&#xff1a; 导入写好的数据库时报错 解决&#xff1a;pip install mysql-python &#xff08;又报错&#xff09; 找了网上的方法&#xff1a; 执行 pip install PyMySQL&#xff0c;将数据库连接改为 mysqlpymysql://username:passwordserver/db&#xff0c;接下来的操…

prompt实用技巧-AI+Mermaid【酷炫钉钉文档】

AI 新技能&#xff0c;最近 chatGPTo1 发布后模型能力出现了新的跨越&#xff0c;之前模型的一本正经的胡说八道幻想模式&#xff0c;让AI 对待理科推理明显弱于文案的 AGI 的生成。 prompt engineer 工程师程序员的福音 prompt 内容如下&#xff0c; 按照以上格式生成创建公…

安卓玩机工具-----ADB与 FASTBOOT模式 图形化 多功能玩机刷机工具

工具说明 这款工具是英文版。易于使用的工具提供了用于运行 ADB 和 Fastboot 命令的图形用户界面。ADB 功能包括旁加载、安装和卸载应用程序、测试设备以及重新启动到不同的模式。可以使用 fastboot 命令进行设备管理;其中包括检查 Antirollback 和 active slots 等变…

鸿蒙 ArkUI组件一

ArkUI组件 布局 布局指用特定的组件或者属性来管理用户页面所放置UI组件的大小和位置。在实际的开发过程中&#xff0c;需要遵守以下流程保证整体的布局效果&#xff1a; 确定页面的布局结构。分析页面中的元素构成。选用适合的布局容器组件或属性控制页面中各个元素的位置和大…

react crash course 2024 (1)理论概念

state的作用 react hooks 而无需写一个class jsx 样式用 spa

Python 数学建模——ARMA 时间序列分析

文章目录 前言使用前提平稳性检验白噪声检验 用法代码实例第一步——平稳性分析方法一方法二方法三 第二步——白噪声分析第三步——确定参数第四步——模型构建与检验检验模型效果预测未来数据 前言 常见的时间序列分析方法有很多&#xff0c;之前介绍了一个稍微新颖的 Prophe…

Springboot项目打war包运行及错误解决

一,打war包 1. 修改pom.xml 为了不影响原pom.xml, 我复制了一个文件叫pom_war.xml , 需要打war包就采用pom_war.xml进行打war包, 你也可以直接修改pom.xml ① 打包方式改为war 没有就增加此配置 <packaging>war</packaging> ② 排除内嵌tomcat依赖 <de…

AI+代码审核平台CodeSec获CCIA中国网络安全创新创业大赛总决赛三等奖

近日&#xff0c;由中央网信办指导&#xff0c;中国网络安全产业联盟&#xff08;CCIA&#xff09;主办的2024年中国网络安全创新创业大赛总决赛及颁奖典礼在国家网络安全宣传周落下帷幕。开源网安“AI代码审核平台CodeSec V4.0” 凭借在AI方向的技术创新、技术突破及功能应用创…

[数据集][目标检测]俯拍航拍森林火灾检测数据集VOC+YOLO格式6116张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;6116 标注数量(xml文件个数)&#xff1a;6116 标注数量(txt文件个数)&#xff1a;6116 标注…

ESP8266做httpServer提示Header fields are too long for server to interpret

CONFIG_HTTP_BUF_SIZE512 CONFIG_HTTPD_MAX_REQ_HDR_LEN1024 CONFIG_HTTPD_MAX_URI_LEN512CONFIG_HTTPD_MAX_REQ_HDR_LEN由512改为1024

基础GAN生成式对抗网络(pytorch实验)

&#xff08;Generative Adversarial Network&#xff09; 一、理论 https://zhuanlan.zhihu.com/p/307527293?utm_campaignshareopn&utm_mediumsocial&utm_psn1815884330188283904&utm_sourcewechat_session 大佬的文章中的“GEN的本质”部分 二、实验 1、数…

【F的领地】项目拆解:少儿英语虚拟资料项目 | 虚拟资料类目 | 学会利用 AI 去生成素材

项目介绍 前几天我分享的小学教辅资料整合项目&#xff0c;已经有学员私信我&#xff0c;说在实操的过程碰到问题了。 我给出了对应的解答&#xff0c;问了下收益&#xff0c;虽然才出十几单&#xff0c;但起码是行动了。 碰到问题&#xff0c;并不可怕&#xff0c;我之前博…