如果我们在不同的go协同例程中访问结构体的互斥字段,是否存在任何并发问题?
我记得在某个地方读到,如果两个并行线程访问同一个对象,它们可能会在cpu的不同核心上运行,这两个核心都有不同的cpu级别缓存和不同的对象副本。(与围棋无关)
下面的代码是否足以实现正确的功能,或者是否需要使用额外的同步机制?
package main
import (
"fmt"
"sync"
)
type structure struct {
x string
y string
}
func main() {
val := structure{}
wg := new(sync.WaitGroup)
wg.Add(2)
go func1(&val, wg)
go func2(&val, wg)
wg.Wait()
fmt.Println(val)
}
func func1(val *structure, wg *sync.WaitGroup) {
val.x = "Test 1"
wg.Done()
}
func func2(val *structure, wg *sync.WaitGroup) {
val.y = "Test 2"
wg.Done()
}
编辑:-对于那些问为什么不通道的人不幸的是,这不是我正在工作的实际代码。两个函数都调用了不同的api,并在一个结构体中获取数据,这些结构体中有一个pragma.DoNotCopy
,问protobuf自动生成器为什么他们认为这是一个好主意。所以这些数据不能通过通道发送,否则我必须创建另一个结构来发送数据或要求linter停止抱怨。或者我可以发送一个指向对象的指针,但感觉它也在共享内存。
当至少有一个对共享资源的访问为写时,必须进行同步。
你的代码正在做写访问,是的,但是不同的结构域有不同的内存位置。所以你没有访问共享的变量。
如果您使用竞赛检测器运行程序,例如:go run -race main.go
将不打印警告。
现在在func1
中添加fmt.Println(val.y)
并再次运行,它将打印:
WARNING: DATA RACE
Write at 0x00c0000c0010 by goroutine 8:
... rest of race warning
在Go中首选的方式是通信内存而不是共享内存。
在实践中,这意味着你应该像我在这篇博文中展示的那样使用Go通道。
https://marcofranssen.nl/concurrency-in-go
如果你真的想要共享内存,你将不得不使用互斥锁。
https://tour.golang.org/concurrency/9
但是,这会导致上下文切换和Go例程同步,从而减慢程序的速度。
使用通道的例子
package main
import (
"fmt"
"time"
)
type structure struct {
x string
y string
}
func main() {
val := structure{}
c := make(chan structure)
go func1(c)
go func2(c)
func(c chan structure) {
for {
select {
case v, ok := <-c:
if !ok {
return
}
if v.x != "" {
fmt.Printf("Received %vn", v)
val.x = v.x
}
if v.y != "" {
fmt.Printf("Received %vn", v)
val.y = v.y
}
if val.x != "" && val.y != "" {
close(c)
}
}
}
}(c)
fmt.Printf("%vn", val)
}
func func1(c chan<- structure) {
time.Sleep(1 * time.Second)
c <- structure{x: "Test 1"}
}
func func2(c chan<- structure) {
c <- structure{y: "Test 2"}
}