当多个goroutine访问同一个结构体的不同字段时,可能会出现并发问题。

huangapple go评论75阅读模式
英文:

Any concurrency issues when multiple goroutines access different fields of the same struct

问题

如果我们在不同的Go协程中访问结构体的互斥字段,是否会出现并发问题?

我记得在某个地方读到过,如果两个并行线程访问同一个对象,它们可能在CPU的不同核心上运行,每个核心都有不同的CPU级缓存,并且具有所讨论对象的不同副本。(与Go无关)

下面的代码是否足以正确实现功能,还是需要使用额外的同步机制?

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停止抱怨。或者我可以发送一个指向对象的指针,但我觉得这也是共享内存的一种方式。

英文:

Are there are any concurrency issues if we access mutually exclusive fields of a struct inside different go co-routines?

I remember reading somewhere that if two parallel threads access same object they might get run on different cores of the cpu both having different cpu level caches with different copies of the object in question. (Not related to Go)

Will the below code be sufficient to achieve the functionality correctly or does additional synchronization mechanism needs to be used?

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()
}

Edit: - for people who ask why not channels unfortunately this is not the actual code I am working on. Both the func has calls to different api and get the data in a struct, those struct has a pragma.DoNotCopy in them ask protobuf auto generator why they thought it was a good idea. So those data can't be sent over the channel, or else i have to create another struct to send the data over or ask the linter to stop complaining. Or i can send a pointer to the object but feel that is also sharing the memory.

答案1

得分: 4

当至少有一个对共享资源的访问是写操作时,你必须进行同步。

你的代码确实进行了写操作,但是不同的结构字段具有不同的内存位置。所以你并没有访问共享变量。

如果你使用竞争检测器运行程序,例如go run -race main.go,它将不会打印警告。

现在在func1中添加fmt.Println(val.y),然后再次运行,它将打印:

警告:数据竞争
由goroutine 8在0x00c0000c0010处进行写操作:
... 其余的竞争警告
英文:

You must synchronize when at least one of accesses to shared resources is a write.

Your code is doing write access, yes, but different struct fields have different memory locations. So you are not accessing shared variables.

If you run your program with the race detector, eg. go run -race main.go it will not print a warning.

Now add fmt.Println(val.y) in func1 and run again, it will print:

WARNING: DATA RACE
Write at 0x00c0000c0010 by goroutine 8:
... rest of race warning

答案2

得分: 0

在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 %v\n", v)
					val.x = v.x
				}
				if v.y != "" {
					fmt.Printf("Received %v\n", v)
					val.y = v.y
				}
				if val.x != "" && val.y != "" {
					close(c)
				}
			}
		}
	}(c)
	fmt.Printf("%v\n", 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"}
}
英文:

The preferred way in Go would be to communicate memory as opposed to share the memory.

In practice that would mean you should use the Go channels as I show in this blogpost.

https://marcofranssen.nl/concurrency-in-go

If you really want to stick with sharing memory you will have to use a Mutex.

https://tour.golang.org/concurrency/9

However that would cause context switching and Go routines synchronization that slows down your program.

Example using channels

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

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 := &lt;-c:
				if !ok {
					return
				}

				if v.x != &quot;&quot; {
					fmt.Printf(&quot;Received %v\n&quot;, v)
					val.x = v.x
				}
				if v.y != &quot;&quot; {
					fmt.Printf(&quot;Received %v\n&quot;, v)
					val.y = v.y
				}
				if val.x != &quot;&quot; &amp;&amp; val.y != &quot;&quot; {
					close(c)
				}
			}
		}
	}(c)
	fmt.Printf(&quot;%v\n&quot;, val)
}

func func1(c chan&lt;- structure) {
	time.Sleep(1 * time.Second)
	c &lt;- structure{x: &quot;Test 1&quot;}
}

func func2(c chan&lt;- structure) {
	c &lt;- structure{y: &quot;Test 2&quot;}
}

huangapple
  • 本文由 发表于 2021年10月16日 18:33:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/69594750.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定