Recursion in golang is giving deadlock or negative WaitGroup counter when using goroutines, channels and sync.Waitgroup

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

Recursion in golang is giving deadlock or negative WaitGroup counter when using goroutines, channels and sync.Waitgroup

问题

我正在尝试使用递归函数找到所有目录的列表。函数的代码如下:

func FindDirs(dir string, nativePartitions []int64, wg *sync.WaitGroup, dirlistchan chan string) {
    fd, err := os.Open(dir)
    if err != nil {
        panic(err)
    }
    filenames, err := fd.Readdir(0)
    if err != nil {
        panic(err)
    }

    for _, i := range filenames {
        var buff bytes.Buffer
        buff.WriteString(dir)
        switch dir {
        case "/":
        default:
            buff.WriteString("/")
        }

        buff.WriteString(i.Name())

        t := new(syscall.Statfs_t)
        err = syscall.Statfs(buff.String(), t)
        if err != nil {
            //fmt.Println("Error accessing", buff.String())
        }
        if checkDirIsNative(t.Type, nativePartitions) && i.IsDir() {
            dirlistchan <- buff.String()
            FindDirs(buff.String(), nativePartitions, wg, dirlistchan)
        } else {
            //fmt.Println(i.Name(), "is not native")
        }
    }
}

在主函数中,我这样调用它:

wg := new(sync.WaitGroup)
dirlistchan := make(chan string, 1000)
wg.Add(1)
go func() {
    filtermounts.FindDirs(parsedConfig.ScanFrom, []int64{filtermounts.EXT4_SUPER_MAGIC}, wg, dirlistchan)
}()

go func() {
    wg.Wait()
    close(dirlistchan)
}()

for i := range dirlistchan {
    fmt.Println(i)
}
wg.Wait()

但是我遇到了一个致命错误:fatal error: all goroutines are asleep - deadlock!

如果我将结果打印出来而不使用通道,或者使用互斥锁和追加到切片的方式,我可以使其工作(通过使用Linux的find命令验证结果是否相同)。请参考下面的函数,其中省略了通道并使用了sync.Mutex和追加。

func FindDirs(dir string, nativePartitions []int64, dirlist *[]string, mutex *sync.Mutex) []string {
    fd, err := os.Open(dir)
    defer fd.Close()
    if err != nil {
        panic(err)
    }
    filenames, err := fd.Readdir(0)
    if err != nil {
        panic(err)
    }

    for _, i := range filenames {
        var buff bytes.Buffer
        buff.WriteString(dir)
        switch dir {
        case "/":
        default:
            buff.WriteString("/")
        }

        buff.WriteString(i.Name())

        t := new(syscall.Statfs_t)
        err = syscall.Statfs(buff.String(), t)
        if err != nil {
            //fmt.Println("Error accessing", buff.String())
        }
        if checkDirIsNative(t.Type, nativePartitions) && i.IsDir() {
            mutex.Lock()
            *dirlist = append(*dirlist, buff.String())
            mutex.Unlock()
            FindDirs(buff.String(), nativePartitions, dirlist, mutex)
        } else {
            //fmt.Println(i.Name(), "is not native")
        }
    }
    return *dirlist
}

但是我无法想出一种使用通道和goroutine的方法。非常感谢您的帮助。

英文:

I am trying to find the list of all directories using a recursive function. The code to the function is

func FindDirs(dir string, nativePartitions []int64, wg *sync.WaitGroup, dirlistchan chan string) {
    // defer wg.Done here will give negative waitgroup panic, commenting it will give negative waitgroup counter panic
	fd, err := os.Open(dir)
	if err != nil {
		panic(err)
	}
	filenames, err := fd.Readdir(0)
	if err != nil {
		panic(err)
	}

	for _, i := range filenames {

		var buff bytes.Buffer
		buff.WriteString(dir)
		switch dir {
		case &quot;/&quot;:
		default:
			buff.WriteString(&quot;/&quot;)
		}

		buff.WriteString(i.Name())
		/*err := os.Chdir(dir)
		if err != nil {
			return err
		}*/

		t := new(syscall.Statfs_t)
		err = syscall.Statfs(buff.String(), t)
		if err != nil {
			//fmt.Println(&quot;Error accessing&quot;, buff.String())
		}
		if checkDirIsNative(t.Type, nativePartitions) &amp;&amp; i.IsDir(){
			dirlistchan &lt;- buff.String()
			FindDirs(buff.String(), nativePartitions, wg, dirlistchan) //recursion happens here
		} else {
			//fmt.Println(i.Name(), &quot;is not native&quot;)
		}


	}
}

and in the main function, I am calling it as

wg := new(sync.WaitGroup)
dirlistchan := make(chan string, 1000)
wg.Add(1)
go func() {
	filtermounts.FindDirs(parsedConfig.ScanFrom, []int64{filtermounts.EXT4_SUPER_MAGIC}, wg, dirlistchan)
}()
		

go func() {
	wg.Wait()
	close(dirlistchan)
}()
for i := range dirlistchan {
	fmt.Println(i)
}
wg.Wait()

and I am getting a

fatal error: all goroutines are asleep - deadlock!

I was able to get this working if I am printing the result instead of using channels, or append to a slice using mutex. (verified with the linux find command to see if the results are same.) Please find the function after omitting channels and using sync.Mutex and append.

func FindDirs(dir string, nativePartitions []int64, dirlist *[]string, mutex *sync.Mutex) []string{


	fd, err := os.Open(dir)
	defer fd.Close()
	if err != nil {
		panic(err)
	}
	filenames, err := fd.Readdir(0)
	if err != nil {
		panic(err)
	}

	for _, i := range filenames {
		var buff bytes.Buffer
		buff.WriteString(dir)
		switch dir {
		case &quot;/&quot;:
		default:
			buff.WriteString(&quot;/&quot;)
		}

		buff.WriteString(i.Name())
		/*err := os.Chdir(dir)
		if err != nil {
			return err
		}*/

		t := new(syscall.Statfs_t)
		err = syscall.Statfs(buff.String(), t)
		if err != nil {
			//fmt.Println(&quot;Error accessing&quot;, buff.String())
		}
		if checkDirIsNative(t.Type, nativePartitions) &amp;&amp; i.IsDir(){
			//dirlistchan &lt;- buff.String()
			mutex.Lock()
			*dirlist = append(*dirlist, buff.String())
			mutex.Unlock()
			//fmt.Println(buff.String())

			FindDirs(buff.String(), nativePartitions, dirlist, mutex)
		} else {
			//fmt.Println(i.Name(), &quot;is not native&quot;)
		}

	}
	return *dirlist
}

But I cannot think of a way to make this work with channels and goroutines. Any help is greatly appreciated.

Note: Here is a link to the golang playground with the code. I couldn't find a workaround to get the syscall thing to work on the playground either. It works on my system though.

Thanks.

答案1

得分: 3

简短回答:你没有“关闭”通道。

修复方法:在调用FindDirs的go例程的开头添加defer wg.Done()

go func() {
    defer wg.Done()
    filtermounts.FindDirs(parsedConfig.ScanFrom, []int64{filtermounts.EXT4_SUPER_MAGIC}, wg, dirlistchan)
}()

为什么会发生这种情况

负责关闭通道的go例程在那里等待wg,上面的代码中没有wg.Done。因此,关闭操作永远不会发生。

现在,for循环在通道上阻塞,等待close或者永远等待一个值,这导致错误:

fatal error: all goroutines are asleep - deadlock!

这是你的代码,可以这样运行:

go run filename.go /path/to/folder

代码

package main

import (
	"bytes"
	"fmt"
	"os"
	"sync"
	"syscall"
)

func main() {

	wg := new(sync.WaitGroup)
	dirlistchan := make(chan string, 1000)
	wg.Add(1)
	go func() {
		defer wg.Done()
		FindDirs(os.Args[1], []int64{61267}, wg, dirlistchan)
	}()

	go func() {
		wg.Wait()
		close(dirlistchan)
	}()
	for i := range dirlistchan {
		fmt.Println(i)
	}
	wg.Wait()

}

func FindDirs(dir string, nativePartitions []int64, wg *sync.WaitGroup, dirlistchan chan string) {
	fd, err := os.Open(dir)
	if err != nil {
		panic(err)
	}
	filenames, err := fd.Readdir(0)
	if err != nil {
		panic(err)
	}

	for _, i := range filenames {

		var buff bytes.Buffer
		buff.WriteString(dir)
		switch dir {
		case "/":
		default:
			buff.WriteString("/")
		}

		buff.WriteString(i.Name())
		/*err := os.Chdir(dir)
		  if err != nil {
		      return err
		  }*/

		t := new(syscall.Statfs_t)
		err = syscall.Statfs(buff.String(), t)
		if err != nil {
			//fmt.Println("Error accessing", buff.String())
		}
		if checkDirIsNative(t.Type, nativePartitions) && i.IsDir() {
			dirlistchan <- buff.String()
			FindDirs(buff.String(), nativePartitions, wg, dirlistchan) //recursion happens here
		} else {
			//fmt.Println(i.Name(), "is not native")
		}

	}
}

func checkDirIsNative(dirtype int64, nativetypes []int64) bool {
	for _, i := range nativetypes {
		if dirtype == i {
			return true
		}
	}
	return false
}

这里找到go.play链接。

英文:

Short answer : You are not closing the channel.

Fix : add defer wg.Done() at beginning of the go routine that calls FindDirs

go func() {
defer wg.Done()
filtermounts.FindDirs(parsedConfig.ScanFrom, []int64{filtermounts.EXT4_SUPER_MAGIC}, wg, dirlistchan)
}()

Why did it happen

The go routine that is responsponsible for closing the channel waits for wg there is no wg.Done in the code above. So close never happens

Now the for loop blocks on the channel for close or a value for ever, this cause the error

fatal error: all goroutines are asleep - deadlock!

So here is your code ,this may be run as

go run filename.go /path/to/folder

Code

package main
import (
&quot;bytes&quot;
&quot;fmt&quot;
&quot;os&quot;
&quot;sync&quot;
&quot;syscall&quot;
)
func main() {
wg := new(sync.WaitGroup)
dirlistchan := make(chan string, 1000)
wg.Add(1)
go func() {
defer wg.Done()
FindDirs(os.Args[1], []int64{61267}, wg, dirlistchan)
}()
go func() {
wg.Wait()
close(dirlistchan)
}()
for i := range dirlistchan {
fmt.Println(i)
}
wg.Wait()
}
func FindDirs(dir string, nativePartitions []int64, wg *sync.WaitGroup, dirlistchan chan string) {
fd, err := os.Open(dir)
if err != nil {
panic(err)
}
filenames, err := fd.Readdir(0)
if err != nil {
panic(err)
}
for _, i := range filenames {
var buff bytes.Buffer
buff.WriteString(dir)
switch dir {
case &quot;/&quot;:
default:
buff.WriteString(&quot;/&quot;)
}
buff.WriteString(i.Name())
/*err := os.Chdir(dir)
if err != nil {
return err
}*/
t := new(syscall.Statfs_t)
err = syscall.Statfs(buff.String(), t)
if err != nil {
//fmt.Println(&quot;Error accessing&quot;, buff.String())
}
if checkDirIsNative(t.Type, nativePartitions) &amp;&amp; i.IsDir() {
dirlistchan &lt;- buff.String()
FindDirs(buff.String(), nativePartitions, wg, dirlistchan) //recursion happens here
} else {
//fmt.Println(i.Name(), &quot;is not native&quot;)
}
}
}
func checkDirIsNative(dirtype int64, nativetypes []int64) bool {
for _, i := range nativetypes {
if dirtype == i {
return true
}
}
return false
}

Find the go.play link here

答案2

得分: 0

如已经提到的,如果你想要主goroutine退出,你应该关闭通道。

实现示例:
func FindDirs函数中,你可以为每个递归的func FindDirs调用创建一个额外的通道,并将该新通道作为参数传递。然后同时监听所有这些新通道,并将字符串转发回该函数在参数中接收到的通道。在所有新通道关闭后,关闭在参数中给定的通道。

换句话说,每个函数调用都应该有自己的通道发送消息。然后将字符串一直转发到主函数。

动态选择的描述在这里:https://stackoverflow.com/questions/19992334/how-to-listen-to-n-channels-dynamic-select-statement

英文:

As has been stated already you should close the channel if you want the main goroutine to exit.

Example of implementation :
In function func FindDirs you could make an additional channel for every recursive func FindDirs call that this function is going to make and pass that new channel in the argument. Then simultaneously listen to all those new channels and forward the strings back to the channel that function got in the argument.
After all new channels has been closed close the channel given in the argument.

In other words every func call should have its own channel that it sends to. The string is then forwarded all the way to main function.

Dynamic select described here : https://stackoverflow.com/questions/19992334/how-to-listen-to-n-channels-dynamic-select-statement

huangapple
  • 本文由 发表于 2017年1月4日 00:24:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/41447907.html
匿名

发表评论

匿名网友

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

确定