【问题标题】:why input.Text() is evaluated in the main goroutine为什么在主 goroutine 中评估 input.Text()
【发布时间】:2019-11-03 09:28:54
【问题描述】:

The Go Programming Language第8章对并发回显服务器有如下描述:

由 go 启动的函数的参数在 go 语句本身执行时被评估;因此 input.Text() 在主 goroutine 中被评估。

我不明白这一点。为什么 input.Text() 在主 goroutine 中被评估?它不应该在 go echo() goroutine 中吗?

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// See page 224.

// Reverb2 is a TCP server that simulates an echo.
package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "strings"
    "time"
)

func echo(c net.Conn, shout string, delay time.Duration) {
    fmt.Fprintln(c, "\t", strings.ToUpper(shout))
    time.Sleep(delay)
    fmt.Fprintln(c, "\t", shout)
    time.Sleep(delay)
    fmt.Fprintln(c, "\t", strings.ToLower(shout))
}

//!+
func handleConn(c net.Conn) {
    input := bufio.NewScanner(c)
    for input.Scan() {
        go echo(c, input.Text(), 1*time.Second)
    }
    // NOTE: ignoring potential errors from input.Err()
    c.Close()
}

//!-

func main() {
    l, err := net.Listen("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }
    for {
        conn, err := l.Accept()
        if err != nil {
            log.Print(err) // e.g., connection aborted
            continue
        }
        go handleConn(conn)
    }
}

代码在这里:https://github.com/adonovan/gopl.io/blob/master/ch8/reverb2/reverb.go

【问题讨论】:

  • 因为语言规范是这么说的。
  • 所有参数在遇到 go 关键字时进行评估,然后 goroutine 从这些计算值开始。如果你不想要这个,你需要启动一个 func 来计算这些值,像这样:go func() { yourFuncWithArgs(arg1.Text(), arg2) }();但是,这通常没有意义。同样的行为也适用于defer

标签: go concurrency goroutine


【解决方案1】:

go 关键字在 Go 中的工作原理,请参阅 Go_statements

函数值参数在调用goroutine中是evaluated as usual,但与常规调用不同的是,程序执行不会等待调用的函数完成。相反,该函数开始在一个新的 goroutine 中独立执行。当函数终止时,它的 goroutine 也会终止。如果函数有任何返回值,则在函数完成时将其丢弃。


函数值参数使用go关键字求值defer关键字相同,参见@ 987654323@)。


要了解评估顺序,让我们试试这个:

go have()(fun("with Go."))

让我们运行 this 并阅读代码 cmets 以获得评估顺序:

package main

import (
    "fmt"
    "sync"
)

func main() {
    go have()(fun("with Go."))

    fmt.Print("some ") // evaluation order: ~ 3
    wg.Wait()
}

func have() func(string) {
    fmt.Print("Go ") // evaluation order: 1
    return funWithGo
}

func fun(msg string) string {
    fmt.Print("have ") // evaluation order: 2
    return msg
}

func funWithGo(msg string) {
    fmt.Println("fun", msg) // evaluation order: 4
    wg.Done()
}

func init() {
    wg.Add(1)
}

var wg sync.WaitGroup

输出:

Go have some fun with Go.

解释 go have()(fun("with Go.")):
首先在此处进行评估:
go have()(...) 首先运行 have() 部分,结果是 fmt.Print("Go ")return funWithGo,然后运行 ​​fun("with Go."),结果是 fmt.Print("have ")return "with Go.";现在我们有go funWithGo("with Go.")

所以最后的 goroutine 调用是 go funWithGo("with Go.")
这是一个启动新 goroutine 的调用,所以我们真的不知道它什么时候会运行。所以有机会运行下一行:fmt.Print("some "),然后我们在这里等待wg.Wait()。现在goroutine运行这个funWithGo("with Go."),结果是fmt.Println("fun", "with Go.")然后是wg.Done();就是这样。

让我们重写上面的代码,只是将命名函数替换为匿名函数,所以这段代码和上面一样:
例如参见:

func have() func(string) {
    fmt.Print("Go ") // evaluation order: 1
    return funWithGo
}

并剪切此代码,选择go have() 中的have 部分并粘贴,然后选择func have() 中的have 部分,然后按键盘上的Delete,然后您将拥有:
This 更漂亮,结果一样,只是用匿名函数替换所有函数:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(1)

    go func() func(string) {
        fmt.Print("Go ") // evaluation order: 1
        return func(msg string) {
            fmt.Println("fun", msg) // evaluation order: 4
            wg.Done()
        }
    }()(func(msg string) string {
        fmt.Print("have ") // evaluation order: 2
        return msg
    }("with Go."))

    fmt.Print("some ") // evaluation order: ~ 3
    wg.Wait()
}

让我用一个简单的例子来解释一下:
1.考虑这个简单的代码:

i := 1
go fmt.Println(i) // 1

这很清楚:输出是1

但如果 Go 设计者决定在 函数运行时评估函数参数,那么没有人知道 i 的值;您可以更改代码中的i(请参见下一个示例)


  1. 现在让我们关闭:
i := 1
go func() {
    time.Sleep(1 * time.Second)
    fmt.Println(i) // ?
}()

输出真的是unknown,如果main goroutine 早点退出,它甚至没有机会运行:唤醒并打印i,即@987654364 @ 本身可能会改变到那个特定的时刻。


  1. 现在让我们这样解决:
i := 1
go func(i int) { 
    fmt.Printf("Step 3 i is: %d\n", i) // i = 1
}(i)

这个匿名函数参数的类型是int,是一个值类型,i的值是已知的,编译器生成的代码压入1的值( i) 到堆栈,所以这个函数,将使用值1,当时间到来时(未来的某个时间)。


  1. 全部(The Go Playground):
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    i := 1
    go fmt.Println(i) // 1 (when = unknown)
    go fmt.Println(2) // 2 (when = unknown)

    go func() { // closure
        time.Sleep(1 * time.Second)
        fmt.Println(" This won't have a chance to run", i) // i = unknown  (when = unknown)
    }()

    i = 3
    wg := new(sync.WaitGroup)
    wg.Add(1)
    go func(i int) {
        defer wg.Done()
        fmt.Printf("Step 3 i is: %d\n", i) // i = 3 (when = unknown)
    }(i)

    i = 4

    go func(step int) { // closure
        fmt.Println(step, i) // i=? (when = unknown)
    }(5)
    i = 5
    fmt.Println(i) // i=5

    wg.Wait()
}

输出:

5
5 5
2
1
Step 3 i is: 3

Go Playground 输出:

5
5 5
1
2
Step 3 i is: 3

您可能会注意到,12 的顺序是随机的,您的输出可能会有所不同(参见代码 cmets)。

【讨论】:

  • 感谢您的快速回复,但我不明白this code您能解释一下输出吗?
  • 我理解重写的模式,但是我不理解输出,重点是输出,而不是如何重写它。比如为什么这是第一个评估,为什么是第二个等等......提前致谢。
  • 参见说明,希望对您有所帮助。
猜你喜欢
  • 2014-05-19
  • 2019-05-24
  • 2015-03-15
  • 2013-09-02
  • 2013-11-11
  • 1970-01-01
  • 2017-02-04
  • 2014-06-17
  • 1970-01-01
相关资源
最近更新 更多