导航

背景

项目中遇到死锁,使用搜索引擎搜索goroutine堆栈中出现的“sync.runtime_Semacquire deadlock”时,搜到一篇说sync.WaitGroup死锁,特此记录一下

当然项目中的问题不是这个导致的,是sync.RWMutex重入导致的

问题复现

func TestReLock(t *testing.T) {
	var wg sync.WaitGroup
	ch := make(chan int, 1000)
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go doSomething(i, wg, ch)
	}
	wg.Wait()
	fmt.Println("all done")
	for i := 0; i < 1000; i++ {
		dd := <-ch
		fmt.Println("from ch:"+strconv.Itoa(dd))
	}
}

func doSomething(index int, wg sync.WaitGroup, ch chan int) {
	defer wg.Done()
	//fmt.Println("start done:" + strconv.Itoa(index))
	//time.Sleep(20 * time.Millisecond)
	ch <- index
}

分析

在golang中结构体传参是传值,而不是传引用,从而在doSomething函数中接收到的是sync.WaitGroup的一个复制,而不是主线程中定义的变量,所以大家都不是操作的同一个对象,因此主线程无法等到WaitGroup完成。

因此解决办法很简单,改成传引用即可,如下所示:

func doSomething(index int, wg *sync.WaitGroup, ch chan int)

noCopy介绍

在测试上面的错误使用示例的时候,会发现IDE在关键词sync.WaitGroup上显示高亮,并提示“'doSomething' passes lock by value: type 'sync.WaitGroup' contains 'interface{}' which is 'sync.Locker' ”,也就是说IDE其实提示了使用不正确。

仔细看sync.WaitGroup实现,会发现其中封装了一个noCopy对象

type WaitGroup struct {
	noCopy noCopy

	// 64-bit value: high 32 bits are counter, low 32 bits are waiter count.
	// 64-bit atomic operations require 64-bit alignment, but 32-bit
	// compilers do not ensure it. So we allocate 12 bytes and then use
	// the aligned 8 bytes in them as state, and the other 4 as storage
	// for the sema.
	state1 [3]uint32
}

  noCopy也是一个结构体,并且实现了Locker接口

type noCopy struct{}

// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

  所以在golang中,如果一个结构体希望禁止用户做值拷贝,可以在结构体中使用一个noCopy变量,golang中有sync.Cond/sync.waitGroup/sync.Pool中使用了noCopy。其实golang中是禁止Locker接口值拷贝,所以sync.Mutex和sync.RWMutex等等类型如果传值也会提示该错误。

golang sync 包中:
sync.Cond
sync.Pool
sync.WaitGroup
sync.Mutex
sync.RWMutex
- ……
禁止拷贝,

golang 没有禁止对实现sync.Locker接口的对象实例赋值进行报错,只是在使用go vet 做静态语法分析时,会提示错误。

参考资料

https://stackoverflow.com/questions/13566065/why-my-golang-channel-raise-dead-lock-error

sync.WaitGroup的使用以及坑