英文:
Elegant way to handle data race caused by passing channel and shared memory
问题
这是一篇关于Go语言中数据竞争模式的案例。
> 混合使用消息传递(通道)和共享内存会使代码复杂且容易发生数据竞争。
以下是一个关于f.err
的数据竞争案例,它可以在Start
和Wait
中被访问到。
另一个问题是当ctx.Done()
在f.ch <- struct{}{}
之前发生时,f.ch <- struct{}{}
可能会永远阻塞。
在Golang中,有什么优雅的方法来处理这个问题?
我首先想到的方法是在Start
函数中添加上下文(context)。
func (f *Future) Wait(ctx context.Context) error {
select {
case <-f.ch:
return nil
case <-ctx.Done():
f.err = ctx.Err()
return ctx.Err()
}
}
func (f *Future) StartWithContext(ctx context.Context) {
go func() {
resp, err := f.f()
select {
case <-ctx.Done():
return
default:
f.resp = resp
f.err = err
f.ch <- struct{}{}
}
}()
}
我们想知道我的解决方案是否有遗漏的地方?或者是否有更好的解决方案来解决这个问题?
英文:
Here is one case of Data Race Patterns in Go
> Mixed use of message passing (channels) and shared memory makes code complex and susceptible to data races
func (f *Future) Start() {
go func() {
resp, err := f.f()
f.resp = resp
f.err = err // data race
f.ch <- struct{}{} // may block forever
}()
}
func (f *Future) Wait(ctx context.Context) error {
select {
case <-f.ch:
return nil
case <-ctx.Done():
f.err = ctx.Err() // data race
return ctx.Err()
}
}
Here is one data race for f.err
, which could be accessed both in Start
and Wait
.
Another issue is that f.ch <- struct{}{}
may block forever when ctx.Done()
happened before f.ch <- struct{}{}
.
What is the elegant way to handle it in Golang?
The idea comes to my mind first as below, add context to Start
function
func (f *Future) Wait(ctx context.Context) error {
select {
case <-f.ch:
return nil
case <-ctx.Done():
f.err = ctx.Err()
return ctx.Err()
}
}
func (f *Future) StartWithContext(ctx context.Context) {
go func() {
resp, err := f.f()
select {
case <-ctx.Done():
return
default:
f.resp = resp
f.err = err
f.ch <- struct{}{}
}
}()
}
We want to know if something missing in my solution? or is there a better solution to this issue?
答案1
得分: 1
第二段代码片段仍然存在数据竞争问题。StartWithContext
可能会先进入 default
分支,然后 Wait
进入 ctx.Done()
分支。
你遇到了共享内存的问题,最明显的解决方法是使用互斥锁(mutex)。此外,请注意你正在将 f.ch
用作完成通道,所以代码如下:
type Future struct {
sync.Mutex
...
}
func (f *Future) Start() {
go func() {
resp, err := f.f()
f.Lock()
f.resp = resp
f.err = err
f.Unlock()
close(f.ch)
}()
}
func (f *Future) Wait(ctx context.Context) error {
select {
case <-f.ch:
return nil
case <-ctx.Done():
f.Lock()
f.err = ctx.Err()
f.Unlock()
return ctx.Err()
}
}
英文:
There is still data race in the second code snippet. It is possible for StartWithContext
to fall to default
case first, and then Wait
falling into the ctx.Done()
case.
You have a shared memory problem, and the most obvious way to deal with it is with a mutex. Also note that you are using f.ch
as a done-channel, so:
type Future struct {
sync.Mutex
...
}
func (f *Future) Start() {
go func() {
resp, err := f.f()
f.Lock()
f.resp = resp
f.err = err
f.Unlock()
close(f.ch)
}()
}
func (f *Future) Wait(ctx context.Context) error {
select {
case <-f.ch:
return nil
case <-ctx.Done():
f.Lock()
f.err = ctx.Err()
f.Unlock()
return ctx.Err()
}
}
</details>
# 答案2
**得分**: 1
使用通道将错误发送给服务员。
将Future更改为:
type Future struct {
ch chan error
// 其他字段与之前相同
}
在Start中发送错误:
func (f *Future) Start() {
go func() {
resp, err := f.f()
f.resp = resp
f.ch <- err
}()
}
在Wait中接收错误:
func (f *Future) Wait(ctx context.Context) error {
select {
case err := <-f.ch:
return err
case <-ctx.Done():
f.err = ctx.Err()
return ctx.Err()
}
}
创建一个带缓冲的通道,以防止在上下文被取消时Start中的goroutine永远阻塞。
f.ch = make(chan error, 1)
<details>
<summary>英文:</summary>
Use the channel to send the error to the waiter.
Change Future to:
type Future struct {
ch chan error
// other fields as before
}
Send the error in Start:
func (f *Future) Start() {
go func() {
resp, err := f.f()
f.resp = resp
f.ch <- err
}()
}
Receive the error in Wait:
func (f *Future) Wait(ctx context.Context) error {
select {
case err := <-f.ch:
return err
case <-ctx.Done():
f.err = ctx.Err()
return ctx.Err()
}
}
Create a buffered channel to prevent the goroutine in Start from blocking forever when the context is canceled.
f.ch = make(chan error, 1)
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论