GO内存模型(同步机制)

news2024/10/7 20:34:44

文章目录

        • 概念
          • 1. 先行发生
        • 编译器重排
        • 同步机制
          • init函数
          • 协程的创建
          • channel
          • sync 包
            • 1. sync.mutex
            • 2. sync.rwmutex
            • 3. sync.once
          • atomic
        • 参考文献

概念

1. 先行发生

The happens before relation is defined as the transitive closure of the union of the sequenced before and synchronized before relations.

翻译:
happens before关系被定义为序列化before关系和同步化before关系的联合的传递闭包。
解析:

  1. happens before关系描述两个事件之间的先后顺序
  2. 序列化before关系表示单个goroutine内事件间的顺序
  3. 同步化before关系表示不同goroutine间由同步原语同步的顺序
  4. happens before关系是上述两个关系的联合
  5. 传递闭包表示如果A happens before B,B happens before C,那么A happens before C

可以理解为程序的执行顺序,在单个协程当中,程序先行发生的顺序就是程序表达的顺序
举个例子:

var a string
func hello() {
	a = "hello, world"
	print(a)
}

我们说a = "hello, world",先行发生于print(a)

var a string

func f() {
	print(a)
}
func f2() {
	a = "hello, world"
}
func hello() {
	go f()
	go f2()
}

在多个协程对共享变量的读写中,为了保证读写的正确性,我们需要引入同步机制保证程序的顺序一致性执行。比如channel,sync、atomic package。在上面的例子中f()有可能打印空字符串或者"hello, world",随机的。print(a)不先行发生于a赋值,a赋值也不先行发生于print(a),我们就说这是并发的。我们应该如何保证f2的写入一定能被f()看到呢?这就是我们要讨论的内容。

编译器重排

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

同步机制

以下是一些我们会用到的让程序保证顺序一致性执行的一些常用手段

init函数

一个函数的初始化函数可能在单个goroutine中执行,但是执行过程中有可能会开启另外一个协程并发执行,这时:
p引入包q,q的init函数结束先行发生于q的所有init函数的开始
所有的init函数执行完了才会执行main函数

协程的创建

新协程的创建先行发生与该协程的执行

var a string

func f() {
	print(a)
}
func hello() {
	a = "hello, world"
	go f()
}

这里f()一定能打印"hello, world",因为a的赋值先行发生于协程的创建,而协程的创建先行发生于函数的执行。

var a string

func f() {
	print(a)
}
func hello() {
	go f()
	a = "hello, world"
}

如果我们调整顺序

var a string

func f() {
	print(a)
}
func hello() {
	go f()
	a = "hello, world"
}

结果是随机的

PS E:\code\go\go_study\advanced\memery_model> go run memery.go
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world

也就是说gorountine 的退出不会保证先行发生于程序的任何事件

channel
  1. 无缓冲channel
    当我们创建的chan 不带缓冲,chan的接收先行发生于chan的发送
var c = make(chan int)
var a string

func f() {
	a = "hello, world"
	<-c
}

func main() {
	go f()
	c <- 0
	print(a)
}

<-c先行发生于c <- 0,所以a一定能打印 "hello, world"

  1. 带缓冲chanel
    当我们创建的chan 带缓冲,当缓冲未满的时候,chan的发送先行发生于chan的接收。当缓冲满了,chan的接收先行发生于chan的发送
var c = make(chan int, 10)
var a string

func f() {
	a = "hello, world"
	c <- 0
}

func main() {
	go f()
	<-c
	print(a)
}

c <- 0先行发生于<-c,所以a一定能打印 "hello, world"

容量为c的带缓冲chan的第k个接收,先行发生于第c+k个发送

var limit = make(chan int, 3)

func main() {
	for _, w := range work {
		go func(w func()) {
			limit <- 1
			w()
			<-limit
		}(w)
	}
	select{}
}

这里当w()执行耗时任务,chan队列满了,第四个任务的 w 的 <-lmit 先行发生于 limit <- 1, 这样就能保证我们队列中只有3个任务在同时执行。

sync 包
1. sync.mutex
var l sync.Mutex
var a string

func f() {
	a = "hello, world"
	l.Unlock()
}

func main() {
	l.Lock()
	go f()
	l.Lock()
	print(a)
}

For any sync.Mutex or sync.RWMutex variable l and n < m, call n of l.Unlock() is synchronized before call m of l.Lock() returns.

l执行了n次Unlock, n次Unlock先行发生于n+1次Lock操作

2. sync.rwmutex

对于同一个sync.RWMutex变量l:

  1. l.RLock() 和 l.RUnlock() 形成配对的读锁定/读解锁操作。
  2. 第n次l.RLock()发生在第n次l.RUnlock()之前。
  3. 第n次l.RUnlock() 先行发生于第n+1次l.Lock()。
  4. 读锁定和读解锁按照配对嵌套的顺序执行。
  5. 每次读解锁先行发生于下一次写锁定。
3. sync.once
var a string
var once sync.Once

func setup() {
	a = "hello, world"
}

func doprint() {
	once.Do(setup)
	print(a)
}

func twoprint() {
	go doprint()
	go doprint()
}

once 可以实现多个协程只执行一次setup代码,并且其他协程会阻塞直到setup函数返回才会继续运行下面的代码。

atomic
go
var counter int64

func worker() {
  for {
    // 原子递增计数器
    atomic.AddInt64(&counter, 1) 
  }
}

func main() {
  for i := 0; i < 10; i++ {
    go worker()
  }

  time.Sleep(time.Second)

  // 原子读取计数器
  c := atomic.LoadInt64(&counter)
  fmt.Println(c)
}

相当于每次执行前都对变量加锁了。比如我们熟悉的i++,汇编成代码其实是3条指令,他是有可能被程序中断的,而atomic 可以实现原子性操作。

参考文献

https://go.dev/ref/mem
https://www.jianshu.com/u/89aa91463068

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

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

相关文章

超详细-Vivado配置Sublime+Sublime实现Verilog语法实时检查

目录 一、前言 二、准备工作 三、Vivado配置Sublime 3.1 Vivado配置Sublime 3.2 环境变量添加 3.3 环境变量验证 3.4 Vivado设置 3.5 配置验证 3.6 解决Vivado配置失败问题 四、Sublime配置 4.1 Sublime安装Package Control 4.2 Sublime安装Verilog插件 4.3 安装语…

centos7中MySQL备份还原策略

目录 一、直接拷贝数据库文件 1.1在shangke主机上停止服务并且打包压缩数据库文件 1.2 在shangke主机上把数据库文件传输到localhost主机上(ip为192.168.33.157) 1.3在localhost主机上停止服务&#xff0c;解压数据库文件 1.4 在localhost主机上开启服务 1.5 测试 二、m…

利用@Excel实现复杂表头导入

EasyPoi导入 <a-upload name"file" :showUploadList"false" :multiple"false" :headers"tokenHeader" :action"importExcelUrl"change"handleImportExcel"><a-button type"primary" icon&quo…

【软件测试】如何选择回归用例

目录 如何在原始用例集中挑选测试用例 具体实践 总结 本文讨论一下在回归测试活动中&#xff0c;如何选择测试用例集。 回归测试用例集包括基本测试用例集&#xff08;原始用例&#xff09;迭代新增测试用例集&#xff08;修复故障引入的用例和新增功能引入的用例集&#xf…

洛必达法则和分部积分的应用之计算数学期望EX--概率论浙大版填坑记

如下图所示&#xff0c;概率论与数理统计浙大第四版有如下例题&#xff1a; 简单说就是&#xff1a;已知两个相互独立工作电子装置寿命的概率密度函数&#xff0c;将二者串联成整机&#xff0c;求整机寿命的数学期望。 这个题目解答中的微积分部分可谓是相当的坑爹&#xff0c;…

【1++的C++初阶】之适配器

&#x1f44d;作者主页&#xff1a;进击的1 &#x1f929; 专栏链接&#xff1a;【1的C初阶】 文章目录 一&#xff0c;什么是适配器二&#xff0c;栈与队列模拟实现三&#xff0c;优先级队列四&#xff0c;reverse_iterator 一&#xff0c;什么是适配器 适配器作为STL的六大组…

【高阶数据结构】跳表

文章目录 一、什么是跳表二、跳表的效率如何保证&#xff1f;三、skiplist的实现四、skiplist跟平衡搜索树和哈希表的对比 一、什么是跳表 skiplist本质上也是一种查找结构&#xff0c;用于解决算法中的查找问题&#xff0c;跟平衡搜索树和哈希表的价值是 一样的&#xff0c;可…

Windows环境Docker安装

目录 安装Docker Desktop的步骤 Docker Desktop 更新WSL WSL 的手动安装步骤 Windows PowerShell 拉取&#xff08;Pull&#xff09;镜像 查看已下载的镜像 输出"Hello Docker!" Docker Desktop是Docker官方提供的用于Windows的图形化桌面应用程序&#xff0c…

区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归多输入单输出区间预测

区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归多输入单输出区间预测 目录 区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归多输入单输出区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 区间预测 | MATLAB实现QRBiLSTM…

odoo16 用好计量单位中的激活功能

odoo16 用好计量单位中的激活功能 根据国内常用&#xff0c;把不常用的单位去除&#xff0c;删除不了&#xff0c;提示已用&#xff0c;其实不用删除&#xff0c;每个单位后有个激活功能&#xff0c;选一下就可以了&#xff0c;显示成整洁的界面了 第一次用时&#xff0c;小伙伴…

解决spring cloud 中使用spring security全局异常处理器失效

写auth认证模块实现忘记密码与注册功能时&#xff0c;用异常抛出&#xff0c;全局异常处理器无法捕获。 无法进行异常捕捉 解决方案&#xff1a;使用WebSecurityConfigurerAdapter.configure中http实现自定义异常&#xff1a; EnableWebSecurity EnableGlobalMethodSecurity(…

87、springcloud核心组件及其作用

spring Eureka: 服务注册与发现 注册:&#xff1a;每个服务都向Eureka登记自己提供服务的元数据&#xff0c;包括服务的ip地址、端口号、版本号、通信协议等 eureka将各个服务维护在了一个服务清单中 (双层Map&#xff0c;第一层key是服务名&#xff0c;第二层key是实例名&…

macOS 源码编译 qpress

╰─➤ git clone https://github.com/PierreLvx/qpress.git ╰─➤ cd qpress ╰─➤ make g -O3 -o qpress -x c quicklz.c -x c qpress.cpp aio.cpp utilities.cpp -lpthread -Wall -Wextra -Werror ╰─➤ sudo make install …

Vue--》打造个性化医疗服务的医院预约系统(三)

今天开始使用 vue3 + ts 搭建一个医院预约系统的前台页面,因为文章会将项目的每一个地方代码的书写都会讲解到,所以本项目会分成好几篇文章进行讲解,我会在最后一篇文章中会将项目代码开源到我的GithHub上,大家可以自行去进行下载运行,希望本文章对有帮助的朋友们能多多关…

Java基础小知识(待续)

类型转换、ASCII码、除法取余、三元表达式 long x 100;//int->long自动类型转换&#xff08;隐式) 1&#xff0e;特点:代码不需要进行特殊处理&#xff0c;自动完成。2&#xff0e;规则:数据范围从小到大。double y 2.5F;//2.5 float->double自动类型转换&#xff08…

pyqt5-Ctrl+鼠标滚轮实现文本区文字大小调整

技术简介 在 PyQt5 中&#xff0c;你可以使用 QTextEdit 的 wheelEvent 方法和 QKeyEvent 的 key() 方法来检测 Ctrl 键和鼠标滚轮事件&#xff0c;从而实现按下 Ctrl 键并滚动鼠标滚轮时&#xff0c;调整 QTextEdit 的字体大小。 这个示例中&#xff0c;我们创建了一个窗口&am…

模板方法模式(java)

目录 结构 案例 代码实现 抽象类 具体子类 测试类 优缺点 优点 缺点 结构 模板方法&#xff08;Template Method&#xff09;模式包含以下主要角色&#xff1a; 抽象类&#xff08;Abstract Class&#xff09;&#xff1a;负责给出一个算法的轮廓和骨架。它由一个模板…

基于SPDK-vhost的云原生Kubevirt虚拟化存储IO的优化方案

摘要 本文主要介绍针对云原生kubernetes虚拟化IO的应用场景&#xff0c;在Kubevirt中引入SPDK-vhost的支持&#xff0c;来加速虚机中IO存储性能。同时基于Intel开源的Workload Service Framework[1]平台集成部署一套端到端虚拟化IO的应用场景做基本的性能对比测试。 云原生Kube…

Failed to load response data:No data found for resource with given identifier

前言 关于跨域的另一种解释 前端Ajax访问后端&#xff0c;表单提交&#xff0c;有一个接口报错&#xff0c;其他都没问题 网上看了很多案例方法&#xff0c;均不适用&#xff1b;早上改代码过程中&#xff0c;改好了&#xff0c;话不多说&#xff0c;上原因 原因 提前关闭页…

CentOS7系统下Docker容器基于TensorFlow测试GPU

前言 当基于nvidia gpu开发的docker镜像在实际部署时&#xff0c;需要先安装nvidia docker。安装nvidia docker前需要先安装原生docker compose 1. CentOS7安装docker详细教程 安装docker 1. Docker 要求 CentOS 系统的内核版本高于 3.10 &#xff0c;查看本页面的前提条件来验…