使用互斥锁和反射时的竞争条件
Race condition when using mutex and reflect
Go 竞态检测器在使用互斥锁时报告竞态条件并反映在被锁定的结构上,示例代码如下。即使反射和对结构成员的访问都通过锁定互斥锁来保护,竞争检测器仍然报告竞争条件。
如何解决争用?
代码:
// main file
package main
import (
"fmt"
"reflect"
"sync"
)
type TestType struct {
counter uint64
lock sync.Mutex
}
func NewTestType() *TestType {
t := &TestType{
counter: 0,
lock: sync.Mutex{},
}
go func() {
t.lock.Lock()
defer t.lock.Unlock()
t.counter++
}()
return t
}
func ItShouldNotRace() string {
t := NewTestType()
t.lock.Lock()
defer t.lock.Unlock()
val := reflect.ValueOf(t)
iface := val.Interface()
return fmt.Sprintf("%v", iface)
}
// test file
package main
import (
"testing"
)
func TestItShouldNotRace(t *testing.T) {
if ItShouldNotRace() == "impossible" {
t.Fail()
}
}
竞争检测器输出:
==================
WARNING: DATA RACE
Read at 0x00c000120078 by goroutine 7:
reflect.typedmemmove()
/usr/local/opt/go/libexec/src/runtime/mbarrier.go:177 +0x0
reflect.packEface()
/usr/local/opt/go/libexec/src/reflect/value.go:120 +0x12f
reflect.valueInterface()
/usr/local/opt/go/libexec/src/reflect/value.go:1045 +0x1cd
reflect.Value.Interface()
/usr/local/opt/go/libexec/src/reflect/value.go:1015 +0x3aa4
fmt.(*pp).printValue()
/usr/local/opt/go/libexec/src/fmt/print.go:726 +0x3aa5
fmt.(*pp).printValue()
/usr/local/opt/go/libexec/src/fmt/print.go:880 +0x25fc
fmt.(*pp).printArg()
/usr/local/opt/go/libexec/src/fmt/print.go:716 +0x26b
fmt.(*pp).doPrintf()
/usr/local/opt/go/libexec/src/fmt/print.go:1030 +0x326
fmt.Sprintf()
/usr/local/opt/go/libexec/src/fmt/print.go:219 +0x73
go_issue.ItShouldNotRace()
/Users/dedalus/Downloads/go_issue/main.go:37 +0x1a4
go_issue.TestItShouldNotRace()
/Users/dedalus/Downloads/go_issue/main_test.go:8 +0x2f
testing.tRunner()
/usr/local/opt/go/libexec/src/testing/testing.go:1127 +0x202
Previous write at 0x00c000120078 by goroutine 8:
sync/atomic.CompareAndSwapInt32()
/usr/local/opt/go/libexec/src/runtime/race_amd64.s:293 +0xb
sync.(*Mutex).lockSlow()
/usr/local/opt/go/libexec/src/sync/mutex.go:129 +0x14b
sync.(*Mutex).Lock()
/usr/local/opt/go/libexec/src/sync/mutex.go:81 +0x84
go_issue.NewTestType.func1()
/Users/dedalus/Downloads/go_issue/main.go:21 +0x47
Goroutine 7 (running) created at:
testing.(*T).Run()
/usr/local/opt/go/libexec/src/testing/testing.go:1178 +0x796
testing.runTests.func1()
/usr/local/opt/go/libexec/src/testing/testing.go:1449 +0xa6
testing.tRunner()
/usr/local/opt/go/libexec/src/testing/testing.go:1127 +0x202
testing.runTests()
/usr/local/opt/go/libexec/src/testing/testing.go:1447 +0x5aa
testing.(*M).Run()
/usr/local/opt/go/libexec/src/testing/testing.go:1357 +0x4eb
main.main()
_testmain.go:43 +0x236
Goroutine 8 (running) created at:
go_issue.NewTestType()
/Users/dedalus/Downloads/go_issue/main.go:20 +0x7a
go_issue.ItShouldNotRace()
/Users/dedalus/Downloads/go_issue/main.go:30 +0x54
go_issue.TestItShouldNotRace()
/Users/dedalus/Downloads/go_issue/main_test.go:8 +0x2f
testing.tRunner()
/usr/local/opt/go/libexec/src/testing/testing.go:1127 +0x202
==================
--- FAIL: TestItShouldNotRace (0.00s)
testing.go:1042: race detected during execution of test
这个数据竞争可以通过使 lock
成为一个指针来解决:
type TestType struct {
counter uint64
lock *sync.Mutex
}
来自 sync.Mutex
文档:
// A Mutex must not be copied after first use.
对 lock
字段使用值类型会导致复制此互斥量。
这里的问题根本不是反射值,你可以用一个更简单的例子来复制比赛:
type TestType struct {
sync.Mutex
}
func main() {
t := &TestType{}
go func() {
t.Lock()
defer t.Unlock()
}()
t.Lock()
defer t.Unlock()
fmt.Printf("%v\n", t)
}
问题是 fmt.Printf
调用将遍历提供的值以格式化输出,并且在该过程中它必须读取 sync.Mutex
值本身。这意味着竞争是在读取互斥量值(互斥量不应该被“读取”,因为它不是可以复制的值。fmt
确实 读取并打印一个私有互斥锁值是一个有争议的错误,但目前还不能真正改变)
如果这是您打算经常传递给 fmt
以用于输出的值,那么我建议添加 String
、GoString
and/or Format
方法在不读取互斥体本身的情况下创建字符串值。
在上面的示例中简单地添加 fmt.Stringer
就可以避免竞争条件。
func (t *TestType) String() string {
return "TestType{}"
}
如果您需要锁定值以便同时读取其他字段,这也很方便。如果我们在您的 counter
字段中添加回来,我们需要序列化该访问(注意您需要删除外部锁定调用以防止此处出现死锁):
func (t *TestType) String() string {
t.Lock()
defer t.Unlock()
return fmt.Sprintf("TestType{counter:%d}", t.counter)
}
避免这种情况的另一种方法是使用互斥锁的指针,但是这会阻止您使用倾向于首选的零值,并且您仍然需要确保在外部锁定该值,以便另一个可以安全地读取字段。由于您已经有了一个构造函数,这可能不是问题,但这是需要考虑的事情
type TestType struct {
*sync.Mutex
}
func NewTestType() *TestType {
return &TestType{
&sync.Mutex{},
}
}
Go 竞态检测器在使用互斥锁时报告竞态条件并反映在被锁定的结构上,示例代码如下。即使反射和对结构成员的访问都通过锁定互斥锁来保护,竞争检测器仍然报告竞争条件。
如何解决争用?
代码:
// main file
package main
import (
"fmt"
"reflect"
"sync"
)
type TestType struct {
counter uint64
lock sync.Mutex
}
func NewTestType() *TestType {
t := &TestType{
counter: 0,
lock: sync.Mutex{},
}
go func() {
t.lock.Lock()
defer t.lock.Unlock()
t.counter++
}()
return t
}
func ItShouldNotRace() string {
t := NewTestType()
t.lock.Lock()
defer t.lock.Unlock()
val := reflect.ValueOf(t)
iface := val.Interface()
return fmt.Sprintf("%v", iface)
}
// test file
package main
import (
"testing"
)
func TestItShouldNotRace(t *testing.T) {
if ItShouldNotRace() == "impossible" {
t.Fail()
}
}
竞争检测器输出:
==================
WARNING: DATA RACE
Read at 0x00c000120078 by goroutine 7:
reflect.typedmemmove()
/usr/local/opt/go/libexec/src/runtime/mbarrier.go:177 +0x0
reflect.packEface()
/usr/local/opt/go/libexec/src/reflect/value.go:120 +0x12f
reflect.valueInterface()
/usr/local/opt/go/libexec/src/reflect/value.go:1045 +0x1cd
reflect.Value.Interface()
/usr/local/opt/go/libexec/src/reflect/value.go:1015 +0x3aa4
fmt.(*pp).printValue()
/usr/local/opt/go/libexec/src/fmt/print.go:726 +0x3aa5
fmt.(*pp).printValue()
/usr/local/opt/go/libexec/src/fmt/print.go:880 +0x25fc
fmt.(*pp).printArg()
/usr/local/opt/go/libexec/src/fmt/print.go:716 +0x26b
fmt.(*pp).doPrintf()
/usr/local/opt/go/libexec/src/fmt/print.go:1030 +0x326
fmt.Sprintf()
/usr/local/opt/go/libexec/src/fmt/print.go:219 +0x73
go_issue.ItShouldNotRace()
/Users/dedalus/Downloads/go_issue/main.go:37 +0x1a4
go_issue.TestItShouldNotRace()
/Users/dedalus/Downloads/go_issue/main_test.go:8 +0x2f
testing.tRunner()
/usr/local/opt/go/libexec/src/testing/testing.go:1127 +0x202
Previous write at 0x00c000120078 by goroutine 8:
sync/atomic.CompareAndSwapInt32()
/usr/local/opt/go/libexec/src/runtime/race_amd64.s:293 +0xb
sync.(*Mutex).lockSlow()
/usr/local/opt/go/libexec/src/sync/mutex.go:129 +0x14b
sync.(*Mutex).Lock()
/usr/local/opt/go/libexec/src/sync/mutex.go:81 +0x84
go_issue.NewTestType.func1()
/Users/dedalus/Downloads/go_issue/main.go:21 +0x47
Goroutine 7 (running) created at:
testing.(*T).Run()
/usr/local/opt/go/libexec/src/testing/testing.go:1178 +0x796
testing.runTests.func1()
/usr/local/opt/go/libexec/src/testing/testing.go:1449 +0xa6
testing.tRunner()
/usr/local/opt/go/libexec/src/testing/testing.go:1127 +0x202
testing.runTests()
/usr/local/opt/go/libexec/src/testing/testing.go:1447 +0x5aa
testing.(*M).Run()
/usr/local/opt/go/libexec/src/testing/testing.go:1357 +0x4eb
main.main()
_testmain.go:43 +0x236
Goroutine 8 (running) created at:
go_issue.NewTestType()
/Users/dedalus/Downloads/go_issue/main.go:20 +0x7a
go_issue.ItShouldNotRace()
/Users/dedalus/Downloads/go_issue/main.go:30 +0x54
go_issue.TestItShouldNotRace()
/Users/dedalus/Downloads/go_issue/main_test.go:8 +0x2f
testing.tRunner()
/usr/local/opt/go/libexec/src/testing/testing.go:1127 +0x202
==================
--- FAIL: TestItShouldNotRace (0.00s)
testing.go:1042: race detected during execution of test
这个数据竞争可以通过使 lock
成为一个指针来解决:
type TestType struct {
counter uint64
lock *sync.Mutex
}
来自 sync.Mutex
文档:
// A Mutex must not be copied after first use.
对 lock
字段使用值类型会导致复制此互斥量。
这里的问题根本不是反射值,你可以用一个更简单的例子来复制比赛:
type TestType struct {
sync.Mutex
}
func main() {
t := &TestType{}
go func() {
t.Lock()
defer t.Unlock()
}()
t.Lock()
defer t.Unlock()
fmt.Printf("%v\n", t)
}
问题是 fmt.Printf
调用将遍历提供的值以格式化输出,并且在该过程中它必须读取 sync.Mutex
值本身。这意味着竞争是在读取互斥量值(互斥量不应该被“读取”,因为它不是可以复制的值。fmt
确实 读取并打印一个私有互斥锁值是一个有争议的错误,但目前还不能真正改变)
如果这是您打算经常传递给 fmt
以用于输出的值,那么我建议添加 String
、GoString
and/or Format
方法在不读取互斥体本身的情况下创建字符串值。
在上面的示例中简单地添加 fmt.Stringer
就可以避免竞争条件。
func (t *TestType) String() string {
return "TestType{}"
}
如果您需要锁定值以便同时读取其他字段,这也很方便。如果我们在您的 counter
字段中添加回来,我们需要序列化该访问(注意您需要删除外部锁定调用以防止此处出现死锁):
func (t *TestType) String() string {
t.Lock()
defer t.Unlock()
return fmt.Sprintf("TestType{counter:%d}", t.counter)
}
避免这种情况的另一种方法是使用互斥锁的指针,但是这会阻止您使用倾向于首选的零值,并且您仍然需要确保在外部锁定该值,以便另一个可以安全地读取字段。由于您已经有了一个构造函数,这可能不是问题,但这是需要考虑的事情
type TestType struct {
*sync.Mutex
}
func NewTestType() *TestType {
return &TestType{
&sync.Mutex{},
}
}