目录:
名词定义
Go或Java中的线程执行主体Goroutine
背景介绍
内存模型的目的是定义不同执行中变量读取和写入的可见性。了解内存模型在并发编程中非常重要,因为代码的执行顺序和编写的逻辑顺序将不完全相同。即使在编译期间,编译器也可能会重新排列代码以优化CPU执行,并且由于存在CPU缓存,内存中的数据可能无法及时更新,因此可能无法对内存中的同一变量进行读写操作。如预期的那样。
类似于Java的内存模型规范,Go语言也具有内存模型。与JMM相比,Go的内存模型相对简单。 Go的并发模型基于CSP(通信顺序过程)。不同的Goroutine传递。称为Channel的数据结构用于通信。 Java的并发模型基于多线程和共享内存,具有更多概念(违规,锁定,最终,构造,线程,原子等)和场景,当然还有java.util.concurrent并发工具该包大大简化了Java并发编程。
Go内存模型指定了条件,在该条件下,变量的Goroutine修改必须对其他Goroutine可见。
发生之前
在单个Goroutine中,变量的读取和写入与代码的写入顺序一致。例如,以下代码:
package main
import (
"log"
)
var a, b, c int
func main() {
a = 1
b = 2
c = a + 2
log.Println(a, b, c)
}
尽管在编译和执行期间,编译器和CPU可能会重新排列代码,例如,先执行b = 2,然后执行a = 1,但是保证c = a + 2在a = 1后执行。这样,最终执行结果必须是1 2 3,而不是1 22。但是以下代码可能会输出0 0 0、1 2 2、0 2 3(b = 2在a = 1之前执行),1 2 3和其他可能性。
package main
import (
"log"
)
var a, b, c int
func main() {
go func() {
a = 1
b = 2
}()
go func() {
c = a + 2
}()
log.Println(a, b, c)
}
发生在定义之前
Happens-before用于指定Go程序中内存操作的本地顺序。如果内存操作事件e1发生在e2之前,那么e2发生在e1之后也为真;如果e1不在e2之前发生,也没有在e2之后发生,则e1和e2是并发的。
在此定义下,如果满足以下条件,则允许变量(v)的内存写操作(w)对内存读操作(r)可见:
r不会在w的开始之前发生(可以在w之后或同时发生);在w和r之间没有发生其他写操作(w’);
为了确保对变量(v)的特定写入操作(w)对于读取操作(r)可见,有必要确保w是r所允许的唯一写入操作,因此如果满足以下条件时,确保变量(v)内存写操作(w)对内存读操作(r)可见:
w在r开始之前发生;对v的所有其他写入操作仅发生在w之前或r之后;
可以看出,后者的协议比前者更为严格。这种情况不需要w或r,也不需要其他并发写入操作。
在单个Goroutine中,由于绝对没有并发性,因此上述两种情况是等效的。变量v的读取操作可以读取上一次写入操作的值(这应该很容易理解)。但是,如果要访问多个Goroutine中的共享变量,则必须使用同步工具建立先发生条件,以确保变量的读取操作可以读取预期的修改值。
为确保并行执行主体对共享变量的顺序访问,请使用锁。 Java和Go在这一点上是一致的。
以下是可以使用的Go语言特定规则。本质上,before-before规则(通过内存屏障和其他指令)确定CPU缓冲区和主内存的同步时间点,以便可以确定变量的读取和写入顺序,这就是我们通常所说的“同步” “。
同步方法初始化如果包p指向包q,则q的init()方法在p之前发生(Java工程师可以比较最终变量的发生在规则之前)main.main()方法在所有包之后发生init ()方法结束。创建Goroutinego语句以创建新的goroutine发生-在执行goroutine之前(这应该很容易理解)
package main
import (
"log"
"time"
)
var a, b, c int
func main() {
a = 1
b = 2
go func() {
c = a + 2
log.Println(a, b, c)
}()
time.Sleep(1 * time.Second)
}
使用这种情况-之前,我们可以确定c = a + 2是发生-aftera = 1和b = 2,因此可以确定结果输出1 2 3,但是如果是以下代码,则输出不确定,可能是1 2 3或0 0 2
func main() {
go func() {
c = a + 2
log.Println(a, b, c)
}()
a = 1
b = 2
time.Sleep(1 * time.Second)
}

销毁Goroutine Goroutine的退出并不能保证在发生任何事件之前发生。
var a string
func hello() {
go func() { a = "hello" }()
print(a)
}
因为在上面的代码中a =“ hello”不使用同步事件,所以不能保证此分配对主goroutine可见。实际上,经过高度优化的Go编译器甚至可以完全删除这一行代码go func(){a =“ hello”}()。
对Goroutine变量的更改需要对其他Goroutines可见。除了使用锁进行同步外,您还可以使用Channel。
频道沟通
在Go编程中,推荐使用Channel作为执行机构之间的通信方法。 Go编译器和运行时将尝试对其进行优化。
发送(发送)到通道的操作发生在相应通道的接收操作完成之前。关闭通道发生在-之前从通道接收到最后的返回值0。无缓冲通道的接收操作(接收)发生在相应通道的发送操作完成之前
var c = make(chan int, 10)
var a string
func f() {
a = "hello, world"
c <- 0
}
func main() {
go f()
<-c
print(a)
}
上面的代码可以确保输出hello,world,因为根据上面的规则1),a =“ hello,world”发生在c之前,print(a)发生在after之后,并且根据上述规则1) ,a =“ hello,world”巧合-beforeprint(a)。
根据规则2),用close(c)替换c还可以保证hello world的输出,因为close操作是在收到0之前发送的。
var c = make(chan int)
var a string
func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}
根据规则3),因为c是不带缓冲的Channel,所以a =“ hello,world”发生-在事件发生之前-在c发生之前-在print(a)之前,但是如果c是一个缓冲队列,则由c = make(chan int,1)定义,则结果不确定。
锁定

sync包实现了两种锁定数据结构:
sync.Mutex-> java.util.concurrent.ReentrantLocksync.RWMutex-> java.util.concurrent.locks.ReadWriteLock
before-before规则与Java相似:
任何sync.Mutex或sync.RWMutex变量(l),定义n
var l sync.Mutex
var a string
func f() {
a = "hello, world"
l.Unlock()
}
func main() {
l.Lock()
go f()
l.Lock()
print(a)
}
a =“ hello,world”发生在l.Unlock()发生之前-第二个l.Lock()发生在print(a)之前
一次
sync软件包还提供了一次安全的初始化工具。还记得Java的Singleton设计模式,仔细检查,甚至三次检查的各种Singleton初始化方法吗? Go提供了一种标准方法。
f()一次。Do(f)发生-之前一次返回任意多个。Do(f)调用,而f()仅有一次调用。
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()
}
尽管上面的代码两次调用doprint(),但实际上安装程序只会执行一次,并发操作一次。Do(setup)将等待安装返回,然后继续。
参考链接
请启用JavaScript以查看由Disqus提供的评论。由博客提供的博客评论
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-360712-1.html
这种确实不会生虫
利息也买不到一斤肉了
懂吗