why input.Text() is evaluated in the main goroutine

前端 未结 1 1944
旧巷少年郎
旧巷少年郎 2021-01-29 12:54

In chapter 8 of The Go Programming Language, there is a description to the concurrency echo server as below:

The arguments to the function started by go a

相关标签:
1条回答
  • 2021-01-29 13:36

    How go keyword works in Go, see Go_statements:

    The function value and parameters are evaluated as usual in the calling goroutine, but unlike with a regular call, program execution does not wait for the invoked function to complete. Instead, the function begins executing independently in a new goroutine. When the function terminates, its goroutine also terminates. If the function has any return values, they are discarded when the function completes.


    The function value and parameters are evaluated in place with the go keyword (same for the defer keyword see an example for defer keyword).


    To understand the evaluation order, let's try this:

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

    Let's run this and read the code comments for the evaluation order:

    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
    
    

    Output:

    Go have some fun with Go.
    

    Explanation go have()(fun("with Go.")):
    First in place evaluation takes place here:
    go have()(...) first have() part runs and the result is fmt.Print("Go ") and return funWithGo, then fun("with Go.") runs, and the result is fmt.Print("have ") and return "with Go."; now we have go funWithGo("with Go.").

    So the final goroutine call is go funWithGo("with Go.")
    This is a call to start a new goroutine so really we don't know when it will run. So there is a chance for the next line to run: fmt.Print("some "), then we wait here wg.Wait(). Now the goroutine runs this funWithGo("with Go.") and the result is fmt.Println("fun", "with Go.") then wg.Done(); that is all.

    Let's rewrite the above code, just replace named functions with anonymous one, so this code is same as above:
    For example see:

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

    And cut this code select the have part in the go have() and paste then select the have part in func have() and press Delete on the keyboard, then you'll have this:
    This is even more beautiful, with the same result, just replace all functions with anonymous functions:

    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()
    }
    

    Let me explain it with a simple example:
    1. Consider this simple code:

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

    This is clear enough: the output is 1.

    But if the Go designers decided to evaluate the function argument at the function run-time nobody knows the value of i; you might change the i in your code (see the next example)


    1. Now let's do this closure:
    i := 1
    go func() {
        time.Sleep(1 * time.Second)
        fmt.Println(i) // ?
    }()
    

    The output is really unknown, and if the main goroutine exits sooner, it even won't have a chance to run: Wake up and print the i, which is i itself may change to that specific moment.


    1. Now let's solve it like so:
    i := 1
    go func(i int) { 
        fmt.Printf("Step 3 i is: %d\n", i) // i = 1
    }(i)
    

    This anonymous function argument is of type int and it is a value type, and the value of i is known, and the compiler-generated code pushes the value 1 (i) to the stack, so this function, will use the value 1, when the time comes (A time in the future).


    1. All (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()
    }
    

    Output:

    5
    5 5
    2
    1
    Step 3 i is: 3
    

    The Go Playground output:

    5
    5 5
    1
    2
    Step 3 i is: 3
    

    As you may be noticed, the order of 1 and 2 is random, and your output may differ (See the code comments).

    0 讨论(0)
提交回复
热议问题