了解使用和不使用 goroutine 从通道中选择

Posted

技术标签:

【中文标题】了解使用和不使用 goroutine 从通道中选择【英文标题】:Understanding select from channels with and without goroutine 【发布时间】:2018-10-09 07:00:09 【问题描述】:

你好 *** 社区, 我正在使用 github.com/fsnotify/fsnotify 将观察者设置为 Go 中的文件。我的功能看起来像

func SetWatcher(filename string) 
    fmt.Println("Setting watcher to file ", filename)
    Watcher, err = fsnotify.NewWatcher()
    if err != nil 
        fmt.Println("inotify errored. Other methods needs to be implemented.")
        panic(err)
    
    if err != nil 
        log.Fatal(err)
    

    done := make(chan bool)
    go func() 
        for 
            select 
            case event := <-Watcher.Events:
                if event.Op == fsnotify.Remove 
                    fmt.Println("File removed, needs to kill the process.")
                 else if event.Op == fsnotify.Rename 
                    fmt.Println("File renamed, need to restart seeking.")
                

            case err := <-Watcher.Errors:
                log.Println("error:", err)
            
        
    ()

    err = Watcher.Add(filename)
    if err != nil 
        log.Fatal(err)
    
    <-done

到目前为止有效,我得到的输出为

Setting watcher to file  /var/log/syslog
File renamed, need to restart seeking.

但是,如果我尝试删除 goroutine 中运行的闭包并将其运行为

func SetWatcher(filename string) 
    fmt.Println("Setting watcher to file ", filename)
    Watcher, err = fsnotify.NewWatcher()
    if err != nil 
        fmt.Println("inotify errored. Other methods needs to be implemented.")
        panic(err)
    
    if err != nil 
        log.Fatal(err)
    
    //defer Watcher.Close()

    //done := make(chan bool)
    //go func() 
    //  for 
            select 
            case event := <-Watcher.Events:
                if event.Op == fsnotify.Remove 
                    fmt.Println("File removed, needs to kill the process.")
                 else if event.Op == fsnotify.Rename 
                    fmt.Println("File renamed, need to restart seeking.")
                

            case err := <-Watcher.Errors:
                log.Println("error:", err)
            
        //
    //()

    err = Watcher.Add(filename)
    if err != nil 
        log.Fatal(err)
    
    //<-done

程序从不输出任何东西。我用strace 运行它,看到程序卡在了

[pid  5773] pselect6(0, NULL, NULL, NULL, tv_sec=0, tv_nsec=20000, NULL <unfinished ...>
[pid  5772] epoll_wait(4, [], 128, 0)   = 0
[pid  5772] futex(0x598bf8, FUTEX_WAIT, 0, NULL <unfinished ...>
[pid  5773] <... pselect6 resumed> )    = 0 (Timeout)
[pid  5740] <... pselect6 resumed> )    = 0 (Timeout)
[pid  5773] futex(0x598578, FUTEX_WAIT, 0, tv_sec=60, tv_nsec=0 
<unfinished ...>
[pid  5740] futex(0xae1f58, FUTEX_WAIT, 0, tv_sec=60, tv_nsec=0

并且没有从频道接收并继续等待。

我了解到这可能是由于非缓冲通道造成的。因此,为了验证这一点,我修改了库以使用缓冲通道,并且生成新观察者的函数部分看起来像

    w := &Watcher
    fd:       fd,
    poller:   poller,
    watches:  make(map[string]*watch),
    paths:    make(map[int]string),
    Events:   make(chan Event, 10), // Made it buffered here
    Errors:   make(chan error),
    done:     make(chan struct),
    doneResp: make(chan struct),

但是行为是一样的。有人可以帮我理解为什么从通道读取在 goroutine 中有效,而在没有 goroutine 的情况下无效?

谢谢。

【问题讨论】:

【参考方案1】:

我对 fsnotify 不熟悉,但在调用Watcher.Add() 观看文件之前,它似乎不会在频道上发送任何内容,但Watcher.Add() 出现在第二个版本中的select 之后。在这种情况下 select 将永远阻塞,因为没有任何信号可以发出信号,因为 Watcher.Add() 还没有被调用。因此出现了死锁。

【讨论】:

以上是关于了解使用和不使用 goroutine 从通道中选择的主要内容,如果未能解决你的问题,请参考以下文章

从 goroutine 通道读取而不阻塞

信号 goroutine 在通道关闭时停止

为啥数据被推入通道但从未从接收器 goroutine 中读取?

选择中的Golang频道未接收

并发——轻量级线程,通道,单向通道

为啥在同一个 goroutine 中使用无缓冲通道会导致死锁?