当使用通道和WaitGroup时,所有Go协程都发生死锁

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

Deadlock on All GoRoutines When Using Channels and WaitGroup

问题

我刚开始学习Go语言,目前正在尝试并发地运行一个创建文件并返回文件名的函数。

我决定使用goroutines和WaitGroup来实现这个目标。但是当我使用这种方法时,最终创建的文件数量比输入的数量要少几百个。例如,对于5000个文件,我只创建了大约4700个文件。

我认为这是由于一些竞争条件引起的:

wg := sync.WaitGroup{}
filenames := make([]string, 0)

for i := 0; i < totalFiles; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        filenames = append(filenames, createFile())
    }()
}

wg.Wait()

return filenames, nil

不要通过共享内存来通信;要通过通信来共享内存。

我尝试使用通道来"通过通信来共享内存"。但是每当我这样做时,似乎会出现一个我无法理解的死锁问题。有人能指导我如何正确地使用通道和WaitGroup来将所有创建的文件保存到共享数据结构中吗?

以下是导致我遇到死锁问题的代码(致命错误:所有的goroutine都处于休眠状态 - 死锁!):

wg := sync.WaitGroup{}
filenames := make([]string, 0)
ch := make(chan string)

for i := 0; i < totalFiles; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        ch <- createFile()
    }()
}

wg.Wait()

for i := range ch {
    filenames = append(filenames, i)
}

return filenames, nil

谢谢!

英文:

I am new to Go and am currently attempting to run a function that creates a file and returns it's filename and have this run concurrently.

I've decided to try and accomplish this with goroutines and a WaitGroup. When I use this approach, I end up with a list size that is a couple hundred files less than the input size. E.g. for 5,000 files I get around 4,700~ files created.

I believe this is due to some race conditions:

wg := sync.WaitGroup{}

filenames := make([]string, 0)

for i := 0; i &lt; totalFiles; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()
		filenames = append(filenames, createFile())
	}()
}

wg.Wait()

return filenames, nil

Don't communicate by sharing memory; share memory by communicating.

I tried using channels to "share memory by communicating". Whenever I do this, there appears to be a deadlock that I can't seem to wrap my head around why. Would anyone be able to point me in the right direction for using channels and waitgroups together properly in order to save all of the created files to a shared data structure?

This is the code that produces the deadlock for me (fatal error: all goroutines are asleep - deadlock!):

wg := sync.WaitGroup{}

filenames := make([]string, 0)
ch := make(chan string)

for i := 0; i &lt; totalFiles; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()
		ch &lt;- createFile()
	}()
}

wg.Wait()

for i := range ch {
	filenames = append(filenames, i)
}

return filenames, nil

Thanks!

答案1

得分: 3

第一个例子中有一个竞争条件。你需要保护对filenames的访问:

mu := sync.Mutex{}
for i := 0; i < totalFiles; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        mu.Lock()
        defer mu.Unlock()
        filenames = append(filenames, createFile())
    }()
}

对于第二个例子,你正在等待goroutine完成,但是goroutine只有在你从通道中读取数据时才能完成,所以会发生死锁。你可以通过在单独的goroutine中从通道中读取数据来修复它。

go func() {
  for i := range ch {
      filenames = append(filenames, i)
  }
}()

wg.Wait()
close(ch) // 必须关闭通道,以便goroutine可以终止

return filenames, nil

如果文件数量是固定的,还有一种无锁版本:

filenames := make([]string, totalFiles)
for i := 0; i < totalFiles; i++ {
    wg.Add(1)
    go func(index int) {
        defer wg.Done()
        filenames[index] = createFile()
    }(i)
}
wg.Wait()

以上是翻译好的内容,请确认是否满意。

英文:

The first one has a race. You have to protect access to filenames:

mu:=sync.Mutex{}
for i := 0; i &lt; totalFiles; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        mu.Lock()
        defer mu.Unlock()
        filenames = append(filenames, createFile())
    }()
}

For the second case, you are waiting for the goroutines to finish, but goroutines can only finish once you read from the channel, so deadlock. You can fix it by reading from the channel in a separate goroutine.

go func() {
  for i := range ch {
      filenames = append(filenames, i)
  }
}()

wg.Wait()
close(ch) // Required, so the goroutine can terminate

return filenames, nil

There is a lock-free version, if the number of files is fixed:

filenames := make([]string, totalFiles)
for i := 0; i &lt; totalFiles; i++ {
    wg.Add(1)
    go func(index int) {
        defer wg.Done()
        filenames[index]=createFile()
    }(i)
}
wg.Wait()

huangapple
  • 本文由 发表于 2022年2月8日 03:39:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/71024407.html
匿名

发表评论

匿名网友

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

确定