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

我从github上的某人那里得到了这段代码,我正在尝试使用它来理解并发性。


package main


import (

    "bufio"

    "fmt"

    "os"

    "sync"

    "time"

)


var wg sync.WaitGroup


func 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)

}


我有几个问题:

  1. 为什么此代码会给我带来死锁?

  2. 在从用户接收输入的操作之前,for循环是如何存在的,go例程是否等到任何内容在urls通道中传递然后开始工作?我没有得到这个,因为它不是连续的,比如为什么从用户那里接收输入,然后将每个输入放在urls通道中,然后运行go例程被认为是错误的?

  3. 在for循环中,我有另一个循环,它正在迭代urls通道,每个go例程是否只处理一行输入?还是一个人一次例行处理多条线?这些是如何工作的?

  4. 我在这里正确收集输出吗?


FFIVE
浏览 173回答 3
3回答

繁花不似锦

大多数情况下,你做事是正确的,但事情有点混乱。循环将继续,直到扫描仪完成,并且循环永远不会运行,因此没有 go 例程(在本例中为“main”)能够从 接收。在运行您的示例时,我之前也启动了循环,并且也在其自己的 go 例程中启动了循环 - 否则将永远无法到达。for sc.Scan()for result := range resultsresultsfor result := range resultsfor sc.Scan()for sc.Scan()go func() {&nbsp; &nbsp; for result := range results {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("%s arrived\n", result)&nbsp; &nbsp; }}()for sc.Scan() {&nbsp; &nbsp; url := sc.Text()&nbsp; &nbsp; urls <- url}另外,因为你之前运行,主戈鲁廷被阻塞等待20个go例程完成。但是他们不能完成,直到被调用。因此,只需在等待等待组之前关闭该通道即可。wg.Wait()close(urls)sad()close(urls)close(urls)wg.Wait()close(results)

梦里花落0921

我对之前的答案不是很满意,所以这里有一个基于go tour,go&nbsp;doc,规范中记录的行为的解决方案。package mainimport (&nbsp; &nbsp; "bufio"&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "strings"&nbsp; &nbsp; "sync"&nbsp; &nbsp; "time")var wg sync.WaitGroupfunc sad(url string) string {&nbsp; &nbsp; fmt.Printf("gonna sleep a bit\n")&nbsp; &nbsp; time.Sleep(2 * time.Millisecond)&nbsp; &nbsp; return url + " added stuff"}func main() {&nbsp; &nbsp; // sc := bufio.NewScanner(os.Stdin)&nbsp; &nbsp; sc := bufio.NewScanner(strings.NewReader(strings.Repeat("blah blah\n", 15)))&nbsp; &nbsp; urls := make(chan string)&nbsp; &nbsp; results := make(chan string)&nbsp; &nbsp; for i := 0; i < 20; i++ {&nbsp; &nbsp; &nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; &nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for url := range urls {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; n := sad(url)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; results <- n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }()&nbsp; &nbsp; }&nbsp; &nbsp; // results is consumed by so many goroutines&nbsp; &nbsp; // we must wait for them to finish before closing results&nbsp; &nbsp; // but we dont want to block here, so put that into a routine.&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; &nbsp; &nbsp; close(results)&nbsp; &nbsp; }()&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for sc.Scan() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; url := sc.Text()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; urls <- url&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; close(urls) // done consuming a channel, close it, right away.&nbsp; &nbsp; }()&nbsp; &nbsp; for result := range results {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("%s arrived\n", result)&nbsp; &nbsp; } // the program will finish when it gets out of this loop.&nbsp; &nbsp; // It will get out of this loop because you have made sure the results channel is closed.}

鸿蒙传说

for 循环创建 20 个 goroutine,所有 goroutine 都在等待来自通道的输入。当有人写信到这个频道时,其中一个戈鲁廷会拿起它并工作。这是典型的工作线程池实现。urls然后,扫描仪逐行读取输入,并将其发送到通道,其中一个goroutine将拾取它并将响应写入通道。此时,没有其他从通道读取的戈鲁丁,因此这将阻塞。urlsresultsresults当扫描程序读取 URL 时,所有其他 goroutine 都会拾取它们并进行阻止。因此,如果扫描程序读取的 URL 超过 20 个,它将死锁,因为所有 goroutine 都将等待读取器。如果 URL 少于 20 个,则扫描程序 for 循环将结束,并将读取结果。然而,这最终也会死锁,因为for循环将在通道关闭时终止,并且没有人在那里关闭通道。要解决此问题,请首先在阅读完后立即关闭频道。这将释放戈鲁丁中的所有for循环。然后,您应该将通道中的 for 循环读数放入 goroutine 中,以便在处理结果时可以调用。之后,您可以关闭通道。urlsresultswg.Waitwg.Waitresults这并不能保证频道中的所有项目都会被读取。程序可能会在处理所有消息之前终止,因此请使用第三个通道,该通道在从通道读取的 goroutine 末尾关闭。那是:resultsresultsdone:=make(chan struct{})go func() {&nbsp; defer close(done)&nbsp; for result := range results {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("%s arrived\n", result)&nbsp; &nbsp; }}()wg.Wait()close(results)<-done
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go