协程是安全的吗?

2023-11-07

前言

我们都知道,多个线程操作同一个变量,是有线程安全问题的。但是,如果换成是“多个协程操作同一个变量”呢?还会有安全问题吗?

实验环境

Windows 11
Go 1.20.2

过程

先看一段Golang代码示例:

func main() {
	count := 0

	wg := sync.WaitGroup{}
	wg.Add(2)
	// 协程1
	go func() {
		for i := 0; i < 100000; i++ {
			count++
		}
		wg.Done()
	}()
	// 协程2
	go func() {
		for i := 0; i < 100000; i++ {
			count++
		}
		wg.Done()
	}()

	wg.Wait() // 等待上面两个协程都执行完了,再往下执行
	fmt.Println(count)
}

上面代码中,协程1 和 协程2 共同对 count 变量执行 +1 操作,各自循环10万遍。

理论上最后输出count的值应该等于20万,但实际输出的值远小于20万。比如我的输出结果是116346

为什么会这样呢,首先是因为 count++ 不是一个原子性的操作,它实际是由三句代码组成的,等同于:

tmp :=  count
tmp = tmp + 1
count = tmp

是一个“先查询后更新”的操作。

然后,Go语言默认情况下是多线程的,线程数量默认等于CPU的核心数。比如双核CPU,Go就会开启两个线程来运行协程,协程1线程A上执行,协程2线程B上执行,由于是多核CPU,这两个线程是可以并行执行的。因此归根到底,这实际上是一个线程安全的问题,即多个线程操作同一个变量导致的。

疑问1

既然是多线程的原因,那如果改成单线程,是不是就不会有问题了呢?

Go语言刚好有提供这样的配置:runtime.GOMAXPROCS(N),其中N就是你想要设置的线程数量。想改为单线程那么只要将N设置为 1 就好。

更改后的代码例子:

func main() {
	runtime.GOMAXPROCS(1) // 设置为单线程
	count := 0

	wg := sync.WaitGroup{}
	wg.Add(2)
	// 协程1
	go func() {
		for i := 0; i < 100000; i++ {
			count++
		}
		wg.Done()
	}()
	// 协程2
	go func() {
		for i := 0; i < 100000; i++ {
			count++
		}
		wg.Done()
	}()

	wg.Wait() // 等待上面两个协程都执行完了,再往下执行
	fmt.Println(count) // 输出:200000
}

果然改为单线程后,就不存在“线程安全”的问题了,能正确输出count的值了。

注:在实际生产环境中,不建议通过设置为单线程模式来避免此类问题,因为单线程会降低Go执行协程的效率,可以通过其它方式,比如加锁、channel之类的方式来解决。

疑问

改为单线程后,就可以毫无顾虑的使用多协程了吗?答:并不是。

在某些场景下如果不注意还是会有隐患的,比如这段代码:

func main() {
	runtime.GOMAXPROCS(1) // 设置为单线程
	count := 0

	wg := sync.WaitGroup{}
	wg.Add(2)
	// 协程1
	go func() {
		for i := 0; i < 100000; i++ {
			count++
		}
		wg.Done()
	}()
	// 协程2
	go func() {
		fmt.Printf("我是协程2,此时count = %d\n", count)
		for i := 0; i < 100000; i++ {
			tmp := count
			tmp = tmp + 1
			count = tmp
		}
		time.Sleep(time.Second * 2) // 此处会迫使协程2让出执行权
		fmt.Printf("我是协程2,已经执行了10万次 +1 操作,此时count = %d\n", count)
		wg.Done()
	}()

	wg.Wait() // 等待上面两个协程都执行完了,再往下执行
}

上述代码会输出:

我是协程2,此时count = 0
我是协程2,已经执行了10万次 +1 操作,此时count = 200000

协程2 一开始查询到的count值是0,执行了10万次+1后,count值居然变成了20万。

这是因为睡眠语句time.Sleep(time.Second * 2)会迫使协程2让出CPU的使用权,让出CPU使用权后,当前线程会改为去执行协程1,当协程1执行完后或遇到 IO阻塞 时,才会又切回来执行协程2,但切回来后,此时的count值已经被协程1修改过了,所以肯定跟协程2刚开始时查询到的值是不一样的。

除了sleep语句外,当协程遇到IO阻塞时,也会让出CPU使用权

所以在协程执行过程中,如果我们想要确保全局变量不会被其它协程修改,就要给变量加锁。

参阅

https://zhuanlan.zhihu.com/p/40279108
https://segmentfault.com/a/1190000041568839

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

协程是安全的吗? 的相关文章

  • 小白学协程笔记2-c语言实现协程-2021-2-10

    文章目录 前言 一 c语言中协程切换方式 二 使用setjmp 和 longjmp实现协程切换 1 setjmp和longjmp函数简介 2 协程实现 三 使用switch case实现协程切换 1 switch case小技巧 2 协程实
  • 【Golang入门】Golang第一天心得

    生活所迫 入门一下Go 很奇葩的第一点 接口 package main import fmt 定义一个接口 type Shape interface Area float64 定义一个矩形类型 type Rectangle struct W
  • 七. go 常见数据结构实现原理之 反射

    目录 一 golang 是如何实现反射的 如何比较两个对象完全相等 一 golang 是如何实现反射的 参考博客Go 语言问题集 Go Questions Go 语言在 reflect 包里定义了各种类型 实现了反射的各种函数 通过它们可以
  • golang基础教程

    目录 golang基础教程 一 环境搭建 golang基础教程 二 开发规范及API golang基础教程 三 变量与数据类型概述 golang基础教程 四 基本数据类型 golang基础教程 五 基本数据类型的转换 golang基础教程
  • go-zero使用Etcd进行服务注册代码分析

    代码分析 github com tal tech go zero v1 2 3 core discov publisher go package discov import github com tal tech go zero core
  • Go语言入门【09】结构体

    结构体 相比于Java 在Go语言中没有类的概念 但是多了结构体 结构体与Java中的类很像 是表示一系列同一类型或不同类型的数据构成的数据集合 例如可以将学生抽象成一个结构体 每一个学生有以下属性 Name 姓名 Age 年龄 Gende
  • 带你使用Golang快速构建出命令行应用程序

    在日常开发中 大家对命令行工具 CLI 想必特别熟悉了 如果说你不知道命令工具 那你可能是个假开发 每天都会使用大量的命令行工具 例如最常用的Git Go Docker等 不管是做技术开发还是业务开发 都会有开发命令行程序的场景 例如如果是
  • golang sleep

    golang的休眠可以使用time包中的sleep 函数原型为 func Sleep d Duration 其中的Duration定义为 type Duration int64 Duration的单位为 nanosecond 为了便于使用
  • 【golang】error parsing regexp: invalid or unsupported Perl syntax (正则表达式校验密码)

    要在 Go 中编写密码校验规则 确保密码不少于8位且包含数字和字母 你可以使用正则表达式和 Go 的 regexp 包来实现 以下是一个示例代码 错误示范 package main import fmt regexp func valida
  • go 进阶 gin实战相关: 五. gin_scaffold 企业脚手架

    目录 一 gin scaffold 企业级脚手架 二 gin scaffold 脚手架安装及使用演示 文件分层解释 开始使用 1 配置开启go mod 功能 2 下载 安装 gin scaffold 3 整合 golang common 4
  • Golang协程与通道整理

    协程goroutine 不由OS调度 而是用户层自行释放CPU 从而在执行体之间切换 Go在底层进行协助实现 涉及系统调用的地方由Go标准库协助释放CPU 总之 不通过OS进行切换 自行切换 系统运行开支大大降低 通道channel 并发编
  • Go_接口、多态、接口继承、空接口、类型断言

    接口 接口是把所有具有共性的方法定义在一起 是方法集 任何类型实现了接口中所有的方法 就是实现了这个接口 接口可以实现多态 接口传递的是地址值 接口定义及调用 定义格式 tepe 接口名 interface 方法名 参数 返回值 调用格式1
  • Golang三剑客之Pflag、Viper、Cobra

    如何构建应用框架 想知道如何构建应用框架 首先你要明白 一个应用框架包含哪些部分 在我看来 一个应用框架需要包含以下 3 个部分 命令行参数解析 主要用来解析命令行参数 这些命令行参数可以影响命令的运行效果 配置文件解析 一个大型应用 通常
  • 基于Go语言实现简易Web应用

    目录 前言 Go语言特点 写在使用Go语言实现Web应用前面 创建Web服务器 声明一个结构体操作 加入中间件的使用 使用静态文件服务器 最后 前言 在编程语言中 近几年问世的几个新语言都是非常不错的 比如Go Python Rust等等
  • go-zero开发入门之gateway深入研究1

    创建一个 gateway 示例 main go package main import flag fmt gateway middleware github com zeromicro go zero core conf github co
  • go-zero 的 etcd 配置

    实现代码在 core discov config go 文件中 type EtcdConf struct Hosts string Key string ID int64 json optional User string json opt
  • GoLong的学习之路,进阶,微服务之序列化协议,Protocol Buffers V3

    这章是接上一章 使用 RPC包 序列化中没有详细去讲 因为这一块需要看的和学习的地方很多 并且这一块是RPC中可以说是最重要的一块 也是性能的重要影响因子 今天这篇主要会讲其使用方式 文章目录 Protocol Buffers V3 背景以
  • GoLong的学习之路,进阶,Viper(yaml等配置文件的管理)

    本来有今天是继续接着上一章写微服务的 但是这几天有朋友说 再写Web框架的时候 遇到一个问题 就是很多的中间件 redis 微信 mysql mq 的配置信息写的太杂了 很不好管理 希望我能写一篇有管理配置文件的 所以这篇就放到今天写吧 微
  • 【go语言】error错误机制及自定义错误返回类型

    简介 Go 语言通过内置的 error 接口来处理错误 该接口定义如下 type error interface Error string 这意味着任何实现了 Error 方法的类型都可以作为错误类型 在 Go 中 通常使用 errors
  • 这套Go语言开发框架组合真的非常高效

    我尝试过很多框架 从Django Flask和Laravel到NextJS和SvelteKit 到目前为止 这是我唯一可以使用的不会让我感到疯狂或者放弃项目的堆栈 框架 我喜欢所有这些框架 但我只是不太适应它们的设计方式 实际上 我是一个弱

随机推荐

  • 图片转换js (img对象,file对象,base64,canvas对象),以及图片压缩方式

    首先想一想我们有哪些需求 大多时候我们需要将一个File对象压缩之后再变为File对象传入到远程图片服务器 有时候我们也需要将一个base64字符串压缩之后再变为base64字符串传入到远程数据库 有时候后它还有可能是一块canvas画布
  • 【blog】使用github-pages搭建个人博客

    我的博客 以此博客记录学习过程及相关学习笔记 一 选择模板 1 在Jekyll Themes 或者jekyll sites 选择一个你喜欢的模板直接下载 2 在github新建一个项目 选择一个主题 外链图片转存失败 源站可能有防盗链机制
  • 数据库的模糊查询

    命中率越高 策略越好 数据库的模糊查询 work918 在SQL中 模糊查询可以使用LIKE关键字来实现 LIKE关键字后面可以跟一个模式 其中 表示任意数量的字符 表示一个字符 例如 如果你想在一个名为students的表中查找所有名字以
  • python计算正方形、立方体、圆、球的面积和体积

    usr bin env python encoding UTF 8 import math 正方形的面积 def square mianji x return x x 立方体的表面积 def cube x return xx6 立方体的体积
  • linux系统下部署02-InfluxDB的安装和设置密码

    InfluxDB是一个当下比较流行的时序数据库 InfluxDB使用 Go 语言编写 无需外部依赖 安装配置非常方便 适合构建大型分布式系统的监控系统 一 InfluxDB 简介 InfluxDB 是用Go语言编写的一个开源分布式时序 事件
  • 使用高效代理抓取58同城巴州二手房信息并保存至excel

    声明 此程序旨在技术学习交流 促进网络安全 不作任何商业用途 违者责任自负 此程序就是使用代理IP来反爬的一个小案例 使用的高效代理 通过API每次请求提取一个代理IP 一个代理IP 必须是高匿代理 隐藏真实IP 相当于一台主机 只要主机足
  • 无需解密代码!软件保护专家VMProtect 2020全新升级!更丰富的保护功能

    VMProtect是新一代的软件保护实用程序 具有内置的反汇编程序 可与Windows和Mac OS X可执行程序配合使用 还可以链接编译器创建的MAP文件 以快速选择代码片段进行保护 VMProtect的基本原则是通过使应用程序代码和逻辑
  • ReactNative——导航器react-navigation(堆栈式导航器篇)

    react navigation 安装核心包 yarn add react navigation native 安装 react navigation native本身依赖的相关包 react native reanimated 动画库 r
  • MVC中前台Model转Json传到后台

    C 代码 string str Newtonsoft Json JsonConvert SerializeObject Model JS代码 var theString str theString theString replace quo
  • C语言实验——求两个整数之和

    C语言实验 求两个整数之和 C语言实验 求两个整数之和 求两个整数之和 不从键盘输入数据 直接使用赋值语句 a 123 b 456 输入数据 然后计算两个整数之和输出 Input 无输入数据 Output 输出a和b之和 Sample Ou
  • 5.Java中的基本数据类型有哪些?

    Java中的基本数据类型有哪些 Java是一个强类型语言 Java中的数据必须明确数据类型 在Java中的数据类型包括基本数据类型和引用数据类型两种 Java中的基本数据类型 数据类型 关键字 内存占用 成员变量初始值 取值范围 整数类型
  • Coursera

    该系列仅在原课程基础上部分知识点添加个人学习笔记 或相关推导补充等 如有错误 还请批评指教 在学习了 Andrew Ng 课程的基础上 为了更方便的查阅复习 将其整理成文字 因本人一直在学习英语 所以该系列以英文为主 同时也建议读者以英文为
  • Qt 判断QString是否为空

    isEmpty QString isEmpty returns true QString isEmpty returns true QString x isEmpty returns false QString abc isEmpty re
  • Linux 存储结构

    软硬链接 windows中的快捷方式 ln 参数 目标 参数 使用 s s表示创建软链接 默认创建的是硬链接 f 强制创建文件或目录的链接 i 覆盖先询问 v 显示创建过程 echo hello wolrd gt readme txt 创建
  • 相关性分析的五种方法

    相关分析 Analysis of Correlation 是网站分析中经常使用的分析方法之一 通过对不同特征或数据间的关系进行分析 发现业务运营中的关键影响及驱动因素 并对业务的发展进行预测 本篇文章将介绍5种常用的分析方法 在开始介绍相关
  • 【H.264/AVC视频编解码技术详解】十二、解析H.264码流的宏块结构(下):H.264帧内编码宏块的预测结构

    H 264 AVC视频编解码技术详解 视频教程已经在 CSDN学院 上线 视频中详述了H 264的背景 标准协议和实现 并通过一个实战工程的形式对H 264的标准进行解析和实现 欢迎观看 纸上得来终觉浅 绝知此事要躬行 只有自己按照标准文档
  • Docker部署nacos单机版

    Docker部署nacos单机版 1 拉取镜像 获取最新nacos docker pull nacos nacos server 获取指定版本的nacos docker pulll nacos nacos server 1 3 0 2 导入
  • 软件工程概述思维导图总结(二)

    软件工程之软件过程 关于作者 作者介绍 博客主页 作者主页 简介 JAVA领域优质创作者 一名在校大三学生 在校期间参加各种省赛 国赛 斩获一系列荣誉 关注我 关注我学习资料 文档下载统统都有 每日定时更新文章 励志做一名JAVA资深程序猿
  • 使用Python分析股价波动周期

    基本思路是获取股价收盘信息后 使用希尔伯特黄变换将股价波动数据拆解为不同周期的波动曲线 再本别利用频谱分析计算每一个曲线的频率 目标是将股价波动数据拆解为不同周期波动的叠加态 1 获取收盘价 富途有很好的API接口 给我这种小散送了每个月的
  • 协程是安全的吗?

    前言 我们都知道 多个线程操作同一个变量 是有线程安全问题的 但是 如果换成是 多个协程操作同一个变量 呢 还会有安全问题吗 实验环境 Windows 11 Go 1 20 2 过程 先看一段Golang代码示例 func main cou