首页 > 系统相关 >Go内存更新问题

Go内存更新问题

时间:2022-12-24 23:56:25浏览次数:68  
标签:wg 缓存 更新 内存 func Go CPU

前言

在开始之前, 先来引出问题. 有这样一段go代码:

func main() {
	var wg sync.WaitGroup
	wg.Add(2)
	var x, y int
	go func() {
		defer wg.Done()
		x = 1
		fmt.Println(fmt.Sprintf("y=%d", y))
	}()
	go func() {
		defer wg.Done()
		y = 1
		fmt.Println(fmt.Sprintf("x=%d", x))
	}()
	wg.Wait()
}

这段代码可能会有哪些结果呢? 无非看语句的执行顺序嘛, 排列组合一下可能的情况:

  • x=1, y=1
  • x=0, y=1
  • x=1, y=1

但是, 如果你多跑几次, 就会发现, x=0, y=0这种我们以为不会出现的情况, 是真的会出现的.

不过这种情况为什么发生想必也都心知肚明, 就简单聊一聊吧.

why

为什么会出现这种情况呢? 其实, 如果了解多线程及 CPU 的实现, 倒也不难理解.

首先, CPU 为了加速, 存在多级缓存. 同样的内存修改会先修改 CPU 内部缓存, 不会立即刷新到内存上.

此时, 若是多核 CPU, 多个线程跑在不同核上, CPU1对内存进行了修改, 但此时修改还在CPU缓存上, 没有刷新到内存, CPU2到内存读到的就是旧值.

同样的, 在Go中, 多个协程也是跑在不同的 CPU 核上, 所以, 内存的更新对其他 CPU 核来说也不是立即可见的. 出现这样的问题也就不奇怪了.

不光是写, 读操作也是有缓存的呦

探究

Java中, 存在volatile关键字, 来保证字段的更新立即刷新到内存. 那么在Go中, 如何来解决这个问题呢?

一说到多线程同步, 第一个想到的必定就是锁了, 没错lock可以保证更新立即可见, 同样的channel atomic 都可以. 其中atomic包做的事情 和volatile是一样的. (也就是说, 前面的例子只要在将读写的操作改为atomic, 就不会出现 x=0,y=0 的情况啦).

Go官方文档中对内存模型进行了简单的介绍, 也说明了这种错误. 甚至于, 在文档中给出了这样的例子(感兴趣的可以去看一下文档, 还挺有趣的):

var a string 
var done bool 
func setup() { 
	a = "hello, world" 
	done = true 
} 
func main() { 
	go setup() 
	for !done { 
	} 
	print(a) 
}
  1. 结果可能打印空字符串. 也就是说, done的更新同步到内存了, 但是a没有
  2. 甚至, 极端情况main的循环可能不会结束. 即CPU 缓存刷新时间很长.

同时, 在这篇官方文档中还有一些很有意思的内容, 推荐读一读. 比如:

  1. 说明了编译期对指令执行顺序的保证
  2. 多协程通信的方式(就是我们已知的几个lock/channel/atomic等)
  3. runtime.SetFinalizer变量的析构函数(但是如果在回收前进程就结束了, 可能不会调用)
  4. build/run命令后跟上-race参数, 可以检测是否存在多协程变量竞争的问题. 若存在, 会在运行时报错.
  5. 等等

over, 对此问题一个简简单单的回顾


原文链接: https://hujingnb.com/archives/879

标签:wg,缓存,更新,内存,func,Go,CPU
From: https://www.cnblogs.com/hujingnb/p/17003562.html

相关文章

  • golang在win10安装、环境配置 和 goland(IDE开发golang配置)
    前言本人在使用goland软件开发go时,对于goland软件配置网上资料少,为了方便自己遗忘、也为了希望和我一样的小白能够更好的使用,所以就写下这篇博客,废话不多说开考。一、查......
  • linux安装go环境
    官方网站下载:​​https://golang.google.cn/dl/go1.19.4.linux-amd64.tar.gz​​wget ​​https://golang.google.cn/dl/go1.19.4.linux-amd64.tar.gz​​tar-zxvf ​​g......
  • django中间件需要了解的方法 importlib模块 django操作cookie django操作session
    目录django中间件三个需要了解的方法process_viewprocess_exceptionprocess_template_response基于django中间件实现功能的插拔式设计importlib模块方式1:基于函数封装方式2......
  • django中只使用ModleForm的表单验证,而不使用ModleForm来渲染
    主题众所周知,django.forms极其强大,不少的框架也借鉴了这个模式,如Scrapy。在表单验证时,django.forms是一绝,也是面向对象的经典表现。但要用它来渲染表单那就不好玩了,除非写......
  • 内存管理函数
    相关函数:malloc头文件 :#include<stdlib.h>函数原型:void*malloc(size_tsize);函数说明:分配内存返回值 :成功返回分配的内存的首地址           ......
  • Go 快速入门指南 - 自增/自减 和 goto 语句
    自增和主流编程语言的自增语法不同,Go只支持 ​​i++​​​ 方式,不支持 ​​++i​​ 方式。正确packagemainfuncmain(){i:=1i++println(i)//输出2}......
  • Go 快速入门指南 - range 遍历
    概述Go特有的一种的遍历结构。它可以遍历任何一个 ​​集合(字符串、数组、切片、Map、通道等)​​​。语法上类似主流编程语言中的 ​​foreach​​ 语句,但可以获得每次......
  • Go 快速入门指南 - 可见性和作用域
    可见性包通过 ​​导出​​ 机制控制 变量、结构体、函数 等数据可见性。只有1个简单的规则: 首字母大写,可导出,首字母小写,不可导出。 也就是说,Go的访问控制只有两......
  • Go 快速入门指南 - 数组
    概述​​数组​​​ 是具有相同数据类型的一组长度固定的数据项序列,分配在连续的内存地址上。其中数据类型可以是整型、布尔型等基础数据类型,也可以是自定义数据类型。 ​......
  • Go 快速入门指南 - 切片
    概述阅读本小节之前,建议先阅读 数组 小节。​​切片​​ 是对数组的一个连续片段的引用。片段可以是整个数组,也可以是数组的一部分(例如数组的第3个元素到第8个元素......