一个递归情况下控制多线程的死锁问题

在多线程递归调用的情况下想要控制线程上限,结果导致了死锁问题于是拿出来讨论一下。

为了方便理解我们假设现在要写一个windows文件夹扫描器。
我们有一扫描函数,可以接收传入路径并对其进行扫描。若扫描结果中有文件夹,则将其路径作为参数递归调用自己。

很明显这里的新线程创建发生在递归调用这里。

然后现在为了控制线程上限,我新建了一个缓冲区。每一个线程都相当于时生产者与消费者。
在新建新线程前生产一个值传入缓冲区内,在自己运行完后从缓冲区内取出一个值消费掉。

那么死锁就产生在了这里。假设我们上限为5,现在同时有五个线程正在扫描对应的文件夹,每个线程又发现了新的文件夹要创建新的线程,于是死锁了。缓冲区已满放不进去,然后卡在了这里,于是每个线程又无法作为消费者将缓冲区内的值消费掉。

有没有什么优雅的解法呢?

我目前想到的一个解法抛弃掉递归,将得出的新路径写入一个单独的队列中,从中再取值新建线程扫描。这样就可以规避这个死锁。

请问大佬们有没有什么其他的优雅解法,有没有可以在递归的情况下的优雅解法?

y1nhui
最佳答案

可以在递归调用自己时增加判断,如果达到最大并发数则不新建goroutine执行,直接在当前g执行

示例代码:

var max = 5
var gC = make(chan bool, max)
var wait = sync.WaitGroup{}

func main() {
    for i := 0; i < max; i++ {
        gC <- true
    }
    //扫描基础目录
    wait.Add(1)
    go f2("base path", true)
    wait.Wait()
}

func f2(path string, goRun bool) {
    dir, _ := ioutil.ReadDir(path)
    for _, file := range dir {
        if file.IsDir() {
            name := path2.Join(path, file.Name())
            select {
            //并发未达到最高值
            case <-gC:
                wait.Add(1)
                go f2(name, true)
            default:
                f2(name, false)
            }
        } else {
            fmt.Println(file.Name())
        }
    }
    if goRun {
        fmt.Printf("done dir %s \n", path)
        wait.Done()
        gC <- true
    }
}
3年前 评论
yinhui (楼主) 3年前
讨论数量: 1

可以在递归调用自己时增加判断,如果达到最大并发数则不新建goroutine执行,直接在当前g执行

示例代码:

var max = 5
var gC = make(chan bool, max)
var wait = sync.WaitGroup{}

func main() {
    for i := 0; i < max; i++ {
        gC <- true
    }
    //扫描基础目录
    wait.Add(1)
    go f2("base path", true)
    wait.Wait()
}

func f2(path string, goRun bool) {
    dir, _ := ioutil.ReadDir(path)
    for _, file := range dir {
        if file.IsDir() {
            name := path2.Join(path, file.Name())
            select {
            //并发未达到最高值
            case <-gC:
                wait.Add(1)
                go f2(name, true)
            default:
                f2(name, false)
            }
        } else {
            fmt.Println(file.Name())
        }
    }
    if goRun {
        fmt.Printf("done dir %s \n", path)
        wait.Done()
        gC <- true
    }
}
3年前 评论
yinhui (楼主) 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!