Search code examples
goconcurrencygoroutine

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


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 are evaluated when the go statement itself is executed; thus input.Text() is evaluated in the main goroutine.

I don't understand this. Why the input.Text() is evaluated at the main goroutine? Shouldn't it be in the 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)
    }
}

code is here: https://github.com/adonovan/gopl.io/blob/master/ch8/reverb2/reverb.go


Solution

  • 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).