使用goroutines,channels和sync时,golang中的递归会给出死锁或负WaitGroup计数器.Waitgroup

时间:2017-01-03 16:24:55

标签: recursion go synchronization channel goroutine

我试图使用递归函数找到所有目录的列表。该函数的代码是

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 "/":
        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")
        }


    }
}

在主函数中,我将其称为

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())
        /*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()
            mutex.Lock()
            *dirlist = append(*dirlist, buff.String())
            mutex.Unlock()
            //fmt.Println(buff.String())

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

    }
    return *dirlist
}

但是我想不出一种方法可以使这些工作与渠道和goroutines。非常感谢任何帮助。

注意:Here是带有代码的golang游乐场的链接。我找不到一个解决方法来让系统调用的东西在操场上工作。它适用于我的系统。

感谢。

2 个答案:

答案 0 :(得分:2)

简短回答:您不是closing频道。

修复:在调用defer wg.Done()

的go例程开始时添加FindDirs
go func() {
    defer wg.Done()
    filtermounts.FindDirs(parsedConfig.ScanFrom, []int64{filtermounts.EXT4_SUPER_MAGIC}, wg, dirlistchan)
}()

为什么会这样?

负责关闭频道的go例程等待 wg ,上面的代码中没有 wg.Done 。如此接近永远不会发生

现在for循环阻止关闭的通道或永远的值,这会导致错误

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链接here

答案 1 :(得分:0)

如前所述,如果您希望主goroutine退出,则应关闭通道。

实施示例: 在函数func FindDirs中,您可以为此函数将要进行的每个递归func FindDirs调用创建一个附加通道,并在参数中传递该新通道。然后同时收听所有这些新频道并将字符串转发回参数中获得的函数通道。 关闭所有新频道后,关闭参数中给出的频道。

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

此处描述的动态选择:how to listen to N channels? (dynamic select statement)