为了账号安全,请及时绑定邮箱和手机立即绑定

为什么这个高浪脚本给我带来了僵局?+ 几个问题

为什么这个高浪脚本给我带来了僵局?+ 几个问题

Go
FFIVE 2022-09-19 17:12:52
我从github上的某人那里得到了这段代码,我正在尝试使用它来理解并发性。package mainimport (    "bufio"    "fmt"    "os"    "sync"    "time")var wg sync.WaitGroupfunc sad(url string) string {    fmt.Printf("gonna sleep a bit\n")    time.Sleep(2 * time.Second)    return url + " added stuff"}func main() {    sc := bufio.NewScanner(os.Stdin)    urls := make(chan string)    results := make(chan string)    for i := 0; i < 20; i++ {        wg.Add(1)        go func() {            defer wg.Done()            for url := range urls {                n := sad(url)                results <- n            }        }()    }    for sc.Scan() {        url := sc.Text()        urls <- url    }    for result := range results {        fmt.Printf("%s arrived\n", result)    }    wg.Wait()    close(urls)    close(results)}我有几个问题:为什么此代码会给我带来死锁?在从用户接收输入的操作之前,for循环是如何存在的,go例程是否等到任何内容在urls通道中传递然后开始工作?我没有得到这个,因为它不是连续的,比如为什么从用户那里接收输入,然后将每个输入放在urls通道中,然后运行go例程被认为是错误的?在for循环中,我有另一个循环,它正在迭代urls通道,每个go例程是否只处理一行输入?还是一个人一次例行处理多条线?这些是如何工作的?我在这里正确收集输出吗?
查看完整描述

3 回答

?
繁花不似锦

TA贡献1851条经验 获得超4个赞

大多数情况下,你做事是正确的,但事情有点混乱。循环将继续,直到扫描仪完成,并且循环永远不会运行,因此没有 go 例程(在本例中为“main”)能够从 接收。在运行您的示例时,我之前也启动了循环,并且也在其自己的 go 例程中启动了循环 - 否则将永远无法到达。for sc.Scan()for result := range resultsresultsfor result := range resultsfor sc.Scan()for sc.Scan()


go func() {

    for result := range results {

        fmt.Printf("%s arrived\n", result)

    }

}()


for sc.Scan() {

    url := sc.Text()

    urls <- url

}

另外,因为你之前运行,主戈鲁廷被阻塞等待20个go例程完成。但是他们不能完成,直到被调用。因此,只需在等待等待组之前关闭该通道即可。wg.Wait()close(urls)sad()close(urls)


close(urls)

wg.Wait()

close(results)


查看完整回答
反对 回复 2022-09-19
?
梦里花落0921

TA贡献1772条经验 获得超6个赞

我对之前的答案不是很满意,所以这里有一个基于go tour,go doc,规范中记录的行为的解决方案

package main


import (

    "bufio"

    "fmt"

    "strings"

    "sync"

    "time"

)


var wg sync.WaitGroup


func sad(url string) string {

    fmt.Printf("gonna sleep a bit\n")

    time.Sleep(2 * time.Millisecond)

    return url + " added stuff"

}


func main() {

    // sc := bufio.NewScanner(os.Stdin)

    sc := bufio.NewScanner(strings.NewReader(strings.Repeat("blah blah\n", 15)))

    urls := make(chan string)

    results := make(chan string)


    for i := 0; i < 20; i++ {

        wg.Add(1)

        go func() {

            defer wg.Done()

            for url := range urls {

                n := sad(url)

                results <- n

            }

        }()

    }

    // results is consumed by so many goroutines

    // we must wait for them to finish before closing results

    // but we dont want to block here, so put that into a routine.

    go func() {

        wg.Wait()

        close(results)

    }()


    go func() {

        for sc.Scan() {

            url := sc.Text()

            urls <- url

        }

        close(urls) // done consuming a channel, close it, right away.

    }()


    for result := range results {

        fmt.Printf("%s arrived\n", result)

    } // the program will finish when it gets out of this loop.

    // It will get out of this loop because you have made sure the results channel is closed.


}


查看完整回答
反对 回复 2022-09-19
?
鸿蒙传说

TA贡献1865条经验 获得超7个赞

for 循环创建 20 个 goroutine,所有 goroutine 都在等待来自通道的输入。当有人写信到这个频道时,其中一个戈鲁廷会拿起它并工作。这是典型的工作线程池实现。urls


然后,扫描仪逐行读取输入,并将其发送到通道,其中一个goroutine将拾取它并将响应写入通道。此时,没有其他从通道读取的戈鲁丁,因此这将阻塞。urlsresultsresults


当扫描程序读取 URL 时,所有其他 goroutine 都会拾取它们并进行阻止。因此,如果扫描程序读取的 URL 超过 20 个,它将死锁,因为所有 goroutine 都将等待读取器。


如果 URL 少于 20 个,则扫描程序 for 循环将结束,并将读取结果。然而,这最终也会死锁,因为for循环将在通道关闭时终止,并且没有人在那里关闭通道。


要解决此问题,请首先在阅读完后立即关闭频道。这将释放戈鲁丁中的所有for循环。然后,您应该将通道中的 for 循环读数放入 goroutine 中,以便在处理结果时可以调用。之后,您可以关闭通道。urlsresultswg.Waitwg.Waitresults


这并不能保证频道中的所有项目都会被读取。程序可能会在处理所有消息之前终止,因此请使用第三个通道,该通道在从通道读取的 goroutine 末尾关闭。那是:resultsresults


done:=make(chan struct{})

go func() {

  defer close(done)

  for result := range results {

        fmt.Printf("%s arrived\n", result)

    }

}()

wg.Wait()

close(results)

<-done


查看完整回答
反对 回复 2022-09-19
  • 3 回答
  • 0 关注
  • 172 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号