源代码与测试代码 https://github.com/YJZhangChina/GNP/tree/master/basic 下 goroutine_traps.go 与 goroutine_traps_test.go
1. 不安全的指针传递
Goroutine的调度次序未知,如果执行多次有指针传递的函数,且函数内存在写操作,需要考虑脏读和原子性问题。定义了Message结构体存储当前值current,valueChange方法用于修改current为当前步骤数step,并用Message指导后续操作。多次运goroutine执行该方法,由于指针传递,Message实例仅有一份,goroutine无法保证函数流程的原子性,造成了写无效和脏读。适当增大循环次数size可更容易发现该问题。
Testing UnsafePointerPassing in GoroutineTraps // 模拟结果可能不同
After step 9 the value is 9.
After step 0 the value is 0.
After step 1 the value is 1.
After step 2 the value is 2.
After step 3 the value is 3.
After step 4 the value is 4.
After step 7 the value is 7.
After step 8 the value is 8.
After step 5 the value is 5.
After step 6 the value is 5. // Error
指针传递前对Message实例进行克隆,产生写时复制的效果,修改后的结果不受影响。
Testing SafePointerPassing in GoroutineTraps // 模拟结果可能不同
After step 0 the value is 0.
After step 9 the value is 9.
After step 5 the value is 5.
After step 6 the value is 6.
After step 7 the value is 7.
After step 8 the value is 8.
After step 2 the value is 2.
After step 1 the value is 1.
After step 3 the value is 3.
After step 4 the value is 4.
goroutine_traps.go
package basic import ( "fmt" "sync" ) type Message struct { current int } func valueChange(step int, message *Message, waiter *sync.WaitGroup) { message.current = step fmt.Printf("After step %d the value is %d. ", step, message.current) waiter.Done() }
goroutine_traps_test.go
package basic import ( "fmt" "sync" "testing" ) func TestUnsafePointerPassing(t *testing.T) { fmt.Println("Testing UnsafePointerPassing in GoroutineTraps") size := 10 var waiter sync.WaitGroup waiter.Add(size) message := &Message{current: 1} for i := 0; i < size; i++ { go valueChange(i+1, message, &waiter) } waiter.Wait() } func TestSafePointerPassing(t *testing.T) { fmt.Println("Testing SafePointerPassing in GoroutineTraps") size := 10 var waiter sync.WaitGroup waiter.Add(size) message := &Message{current: 1} for i := 0; i < 10; i++ { var clone = new(Message) *clone = *message go valueChange(i+1, clone, &waiter) } waiter.Wait() }