58. 不理解竞态问题

内容

竞态问题可能程序员面临的最困难和最隐蔽的错误之一。作为 Go 开发者,必须理解数据竞争和竞态条件等关键方面,包括它们可能产生的影响以及如何避免。接下来将首先讨论数据竞争与竞态条件的区别,然后研究 Go 内存模型及其重要性。

数据竞争与竞态条件

让我们首先关注数据竞赛。当两个或多个 goroutine 同时访问同一内存位置并且至少有一个正在写入时,就会发生数据争用。下面是一个示例,其中两个 goroutine 递增一个共享变量:

i := 0
go func() {i++
}()
go func() {i++
}()

如果我们使用 Go 语言的竞态检测工具(通过 -race 选项)来运行相关代码,该工具会警告我们出现了数据竞争的问题。

==================
WARNING: DATA RACE
Write at 0x00c00008e000 by goroutine 7:main.main.func2()
Previous write at 0x00c00008e000 by goroutine 6:main.main.func1()
==================

最终的结果也是不可预测的,有时可能是1,有时可能是2。
这段代码存在什么问题? i++ 语句可以分解为三个操作。

  1. 读取i,
  2. 对个值进行加,
  3. 将值写会i.

假设在某种情况下,第一个 goroutine 在第二个 goroutine 之前执行并完成,接下来将会说明发生的事情。
image.png
第一个 goroutine 读取 i 的值,将其递增,并将结果 1 写回 i。然后第二个 goroutine 执行相同的操作,但它开始时 i 的值为 1,最终将结果 2 写回 i
然而,不能保证第一个 goroutine 会在第二个 goroutine 之前开始或完成,还可能出现两个 goroutine 交叉并发执行、竞争访问共享变量 i 的情况,并提出接下来要介绍另一种可能的场景。
image.png
首先,两个 goroutine 都从 i 读取到值 0,然后都将其递增并把本地计算结果 1 写回,导致结果不是预期的。这可能就是数据竞争产生的影响,当两个 goroutine 同时访问同一内存位置且至少有一个进行写入时,结果可能是危险的,在某些情况下,该内存位置最终可能会保存一个无意义的位组合值。
接下来,我们来看一下,可以通过哪些技术手段来避免数据竞争的发生。
第一种方法是使递增操作具有原子性(atomic),即在单个操作中完成,以防止并发操作相互干扰。
image.png
即使第二个 goroutine 在第一个之前运行,结果仍然是 2
原子操作在 Go 语言中可以使用 sync/atomic 包,这是一个我们如何以原子方式递增 int64 的示例:

var i int64go func() {atomic.AddInt64(&i, 1)
}()
go func() {atomic.AddInt64(&i, 1)
}()

sync/atomic 包中提供了 int32, int64, uint32, and uint64原子操作,但是没有提供int的原子操作。
sync/atomic 包仅适用于特定类型,如果涉及到切片、映射和结构体等类型,就不能依赖 sync/atomic

另一种防止数据竞争的选项,即使用像互斥锁(Mutex)这样的特定数据结构来同步两个 goroutine。互斥锁的作用是实现互斥访问,确保最多只有一个 goroutine 可以访问所谓的临界区。下边i++就是临界区,通过mutex来保证,无论goroutine的顺序如何,最终的结果都是2。

i := 0
mutex := sync.Mutex{}
go func() {mutex.Lock()i++mutex.Unlock()
}()
go func() {mutex.Lock()i++mutex.Unlock()
}()

另一种防止数据竞争的方式,即避免共享相同的内存位置,而是在 goroutine 之间进行通信。例如,创建一个通道,每个 goroutine 都使用该通道来产生递增的值。

i := 0
ch := make(chan int)
go func() {ch <- 1
}()
go func() {ch <- 1
}()
i += <-ch
i += <-chfmt.Println(i)

每个goroutine通过channel发送一个通知,将i增加1,父goroutine收到信号后,将i的值增加1,这样就避免了数据竞争。
上文小结,关于避免数据竞争的常用操作

  • 使用atomic操作:通过原子性的操作来确保对共享数据的并发访问是安全的。
  • 使用互斥锁(mutex)保护临界区:确保在同一时间只有一个goroutine 可以访问被保护的临界区(包含对共享数据的操作)。
  • 使用channel通信:利用channel进行 goroutine 之间的通信,保证只有一个 goroutine 对特定变量进行更新操作。

有了对上述内容的了解,是否意味着,所有的没有数据竞争的程序结果都是符合预期的了。来看下一段程序:

i := 0
mutex := sync.Mutex{}
go func() {mutex.Lock()defer mutex.Unlock()i = 1
}()
go func() {mutex.Lock()defer mutex.Unlock()i = 2
}()

这段代码通过mutex避免了同一刻访问相同的内存资源,所以没有数据竞争,那么是否意味这个结果一定是2?答案是否定的,该程序强依赖于两个goroutine执行的先后顺序,这就是竟态条件。**「竞态条件」**是指行为取决于无法控制的事件的序列或时间,在这里事件的时间是 goroutine 的执行顺序。如果要保证特定的执行顺序,可通过channel进行解决。同时也就不需要互斥锁了。
内容小结:
数据竞争是多个 goroutine 同时访问同一内存位置且至少有一个进行写入操作;数据竞争会导致意外行为。没有数据竞争的应用程序不一定有确定的结果,当应用程序没有数据竞争,但行为取决于不可控事件(如 goroutine 执行顺序、消息发布到通道的速度、数据库调用持续时间等)时,就存在竞态条件。

go的内存模型

  • 创建先于执行。

创建一个 goroutine 的操作先于该 goroutine 的执行开始。因此,读取一个变量,然后启动一个新的 goroutine 对该变量进行写入,不会导致数据竞争。

i := 0
go func() {i++ 
}()
  • 退出不保证顺序

不能保证一个 goroutine 的退出先于任何事件发生。因此,下面的示例存在数据竞争。

i := 0
go func() {i++
}()
fmt.Println(i)
  • 发送优先于接收

在通道上的发送操作先于相应的从该通道接收操作的完成而发生。在接下来的示例中,一个父 goroutine 在发送之前先递增一个变量,而另一个 goroutine 在通道接收之后读取该变量。

i := 0
ch := make(chan struct{})
go func() {<-chfmt.Println(i)
}()
i++
ch <- struct{}{}
  • 关闭优先于关闭信息的接收

如果 goroutine A 关闭了一个通道,之后 goroutine B 从该通道接收数据,那么 goroutine A 关闭通道这一事件在顺序上先于 goroutine B 对通道关闭的感知和相应的接收操作。

i := 0
ch := make(chan struct{})
go func() {<-chfmt.Println(i)
}()
i++
close(ch)
  • 有缓冲区的发送依赖于接收

父协程发送完数据,然后读取变量i,同时子协程同时写入变量i,然后接收channel中的信息,这里是有数据竞争的。
image.png

i := 0
ch := make(chan struct{}, 1)
go func() {i = 1<-ch
}()
ch <- struct{}{}
fmt.Println(i)

如果我们需要保证顺序的话,可以改为无缓冲区的channel。

	i := 0ch := make(chan struct{})go func() {i = 1<-ch}()ch <- struct{}{}fmt.Println(i)

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

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

相关文章

SpringBoot常用功能实现

1. 配置文件多环境配置 1.1 创建不同环境配置文件 文件名前缀和后缀为标准固定格式&#xff0c;不可以改变。 1.2 pom中加入文件配置 可以使用<activation>标签设置默认环境。 <profiles><profile><id>dev</id><activation><active…

Typora 1.5.8 版本安装下载教程 (轻量级 Markdown 编辑器),图文步骤详解,免费领取(软件可激活使用)

文章目录 软件介绍软件下载安装步骤激活步骤 软件介绍 Typora是一款基于Markdown语法的轻量级文本编辑器&#xff0c;它的主要目标是为用户提供一个简洁、高效的写作环境。以下是Typora的一些主要特点和功能&#xff1a; 实时预览&#xff1a;Typora支持实时预览功能&#xff0…

在 CentOS 7 上安装 Docker 并安装和部署 .NET Core 3.1

1. 安装 Docker 步骤 1.1&#xff1a;更新包索引并安装依赖包 先安装yum的扩展&#xff0c;yum-utils提供了一些额外的工具&#xff0c;这些工具可以执行比基本yum命令更复杂的任务 sudo yum install -y yum-utils sudo yum update -y #更新系统上已安装的所有软件包到最新…

【spring boot】初学者项目快速练手

项目视频&#xff1a;一小时带你从0到1实现一个SpringBoot项目开发_哔哩哔哩_bilibili 注解视频&#xff1a;10、Java高级技术&#xff1a;注解&#xff1a;认识注解_哔哩哔哩_bilibili 一、基础知识 1.注解Annotation &#xff08;1&#xff09;定义 注解是Java代码里的特…

Golang | Leetcode Golang题解之第257题二叉树的所有路径

题目&#xff1a; 题解&#xff1a; func binaryTreePaths(root *TreeNode) []string {paths : []string{}if root nil {return paths}nodeQueue : []*TreeNode{}pathQueue : []string{}nodeQueue append(nodeQueue, root)pathQueue append(pathQueue, strconv.Itoa(root.V…

干货-并发编程提高——线程切换基础(一)

现在的时分&#xff08;time-sharing&#xff09;多任务&#xff08;multi-task&#xff09;操作系统架构通常都是用所谓的“时间分片&#xff08;time quantum or time slice&#xff09;”方式进行抢占式&#xff08;preemptive&#xff09;轮转调度&#xff08;round-robin式…

HydraRPC: RPC in the CXL Era——论文阅读

ATC 2024 Paper CXL论文阅读笔记整理 问题 远程过程调用&#xff08;RPC&#xff09;是分布式系统中的一项基本技术&#xff0c;它允许函数在远程服务器上通过本地调用执行来促进网络通信&#xff0c;隐藏底层通信过程的复杂性简化了客户端/服务器交互[15]。RPC已成为数据中心…

【iOS】内存五大分区

目录 堆&#xff08;Heap&#xff09;是什么五大分区栈区堆区全局/静态区常量区&#xff08;即.rodata&#xff09;代码区&#xff08;.text&#xff09; 函数栈堆和栈的区别和联系图解 OC语言是C语言的超集&#xff0c;所以先了解C语言的内存模型的内存管理会有很大帮助。C语言…

PHP接入consul,注册服务和发现服务【学习笔记】

PHP接入consul,注册服务和发现服务 consul安装 链接: consul安装 启动consul C:\Users\14684>consul agent -dev安装TP5 composer create-project topthink/think5.0.* tp5_pro --prefer-dist配置consul 创建tp5_pro/application/service/Consul.php <?php /*****…

《昇思25天学习打卡营第25天|文本解码原理--以MindNLP为例》

文本解码是自然语言处理&#xff08;NLP&#xff09;中的一个关键步骤&#xff0c;用于将模型生成的向量表示转化为可读的文本。 文本解码的基本原理 在 NLP 中&#xff0c;解码过程通常从模型输出的概率分布或嵌入向量开始&#xff0c;通过某种策略将这些概率或嵌入转化为…

html改写vue日志

本人最近学了vue&#xff0c;想着练手的方法就是改写之前在公司开发的小系统前端&#xff0c;将前端的AJAXJSThymeleaf改为axiosvue。 改写html 将<html>中的<head>和<body>结构移除&#xff0c;将css部分移入<style>&#xff0c; 重新定义了全局的&…

《昇思25天学习打卡营第21天|Pix2Pix实现图像转换》

Pix2Pix 是一种图像转换模型&#xff0c;使用条件生成对抗网络&#xff08;Conditional Generative Adversarial Networks&#xff0c;cGANs&#xff09;实现图像到图像的转换。它主要由生成器&#xff08;Generator&#xff09;和判别器&#xff08;Discriminator&#xff09;…

【Linux】深入探索`cp`命令:文件复制的全面指南

文章目录 一、cp命令概述二、cp命令的基本用法1. 复制单个文件2. 复制多个文件到目录 三、cp命令的常用选项1. -i&#xff1a;交互式复制&#xff08;interactive&#xff09;2. -r或-R&#xff1a;递归复制目录&#xff08;recursive&#xff09;3. -v&#xff1a;详细模式&am…

DAY05 CSS

文章目录 1 CSS选择器(Selectors)8. 后代(包含)选择器9. 直接子代选择器10. 兄弟选择器11. 相邻兄弟选择器12. 属性选择器 2 伪元素3 CSS样式优先级1. 相同选择器不同样式2. 相同选择器相同样式3. 继承现象4. 选择器不同权值的计算 4 CSS中的值和单位1. 颜色表示法2. 尺寸表示法…

2024.7.22 作业

1.将双向链表和循环链表自己实现一遍&#xff0c;至少要实现创建、增、删、改、查、销毁工作 循环链表 looplinklist.h #ifndef LOOPLINKLIST_H #define LOOPLINKLIST_H#include <myhead.h>typedef int datatype;typedef struct Node {union {int len;datatype data;}…

41 QOS技术(服务质量)

1 QOS 产生背景 对于网络业务&#xff0c;影响服务质量的因素包括传输的带宽、传送的时延、数据的丢包率等。网络资源总是有限的&#xff0c;只要存在抢夺网络资源的情况&#xff0c;就会出现服务质量的要求网络总带宽固定的情况下&#xff0c;如果某类业务占用的带宽越多&am…

2024导游资格考试,这些材料提前准备✅

2024年导游考试报名本月开始&#xff01; &#x1f499;大家提前准备好报名材料 1、个人近期白底1寸证件照。 2、身份证照片 3、学历照片 4、健康证明或健康承诺书 5、其他需要上传的材料 &#x1f499;照片文件不通过原因汇总&#xff0c;记得避开这些坑&#xff01; &#x1…

深入浅出理解 C 语言中的 qsort 函数

目录 引言 一、什么是qsort 二、函数原型 1.qsort函数 2.比较函数 三、qsort函数使用示例 1.使用qsort排序整形数据 2.使用qsort排序结构数据 总结 引言 在编程中&#xff0c;排序是一个常见且重要的操作。C 语言标准库提供了一系列排序函数&#xff0c;其中 qsort 函…

嵌入式C++、STM32、树莓派4B、OpenCV、TensorFlow/Keras深度学习:基于边缘计算的实时异常行为识别

1. 项目概述 随着物联网和人工智能技术的发展,智能家居安全系统越来越受到人们的关注。本项目旨在设计并实现一套基于边缘计算的智能家居安全系统,利用STM32微控制器和树莓派等边缘设备,实时分析摄像头数据,识别异常行为(如入侵、跌倒等),并及时发出警报,提高家庭安全性。 系…

HALSTM32 SPI/ABZ/PWM方式读取MT6816磁编码器数据

HALSTM32 SPI方式读取MT6816磁编码器数据 &#x1f4da;MT6816相关资料&#xff08;来自商家的相关资料&#xff09;&#xff1a; 资料&#xff1a;https://pan.baidu.com/s/1CAbdLBRi2dmL4D7cFve1XA?pwd8888 提取码&#xff1a;8888&#x1f4cd;驱动代码编写&#xff0c;可…