为什么 input.Text() 在主 goroutine 中被评估
why input.Text() is evaluated in the main goroutine
在The Go Programming Language的第8章中,对并发回显服务器的描述如下:
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.
我不明白这个。为什么 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
关键字在 Go 中的工作原理,请参阅
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.
函数值和参数计算 go
] 关键字(与 defer
关键字相同,参见 )。
要了解评估顺序,让我们试试这个:
go have()(fun("with Go."))
让我们运行 this 阅读评估顺序的代码注释:
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()
部分运行s结果为fmt.Print("Go ")
和return funWithGo
,然后fun("with Go.")
运行s,结果是 fmt.Print("have ")
和 return "with Go."
;现在我们有 go funWithGo("with Go.")
。
所以最后的 goroutine 调用是 go funWithGo("with Go.")
这是启动一个新的 goroutine 的调用,所以我们真的不知道它什么时候会 运行。所以下一行有机会运行:fmt.Print("some ")
,那我们就在这里等wg.Wait()
。现在 goroutine 运行s this funWithGo("with Go.")
结果是 fmt.Println("fun", "with Go.")
then wg.Done()
;就是这样。
让我们重写上面的代码,只是将命名函数替换为匿名函数,所以这段代码与上面相同:
例如见:
func have() func(string) {
fmt.Print("Go ") // evaluation order: 1
return funWithGo
}
并剪切此代码 select go have()
中的 have
部分,然后粘贴 select 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 设计者决定在 函数参数 运行-time 时计算函数参数,没有人知道 i
的值;您可以更改代码中的 i
(参见下一个示例)
- 现在让我们做这个闭包:
i := 1
go func() {
time.Sleep(1 * time.Second)
fmt.Println(i) // ?
}()
输出真的是未知,如果main
goroutine早点退出,甚至连运行:唤醒的机会都没有并打印 i
,这是 i
本身可能会更改到那个特定时刻。
- 现在让我们这样解决:
i := 1
go func(i int) {
fmt.Printf("Step 3 i is: %d\n", i) // i = 1
}(i)
这个匿名函数参数是int
类型,是值类型,i
的值是已知的,编译器生成的代码 将值 1
(i
) 压入堆栈,因此此函数将在时间到来时(将来的某个时间)使用值 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
您可能会注意到,1
和 2
的顺序是随机的,您的输出可能会有所不同(请参阅代码注释)。
在The Go Programming Language的第8章中,对并发回显服务器的描述如下:
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.
我不明白这个。为什么 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
关键字在 Go 中的工作原理,请参阅
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.
函数值和参数计算 go
] 关键字(与 defer
关键字相同,参见
要了解评估顺序,让我们试试这个:
go have()(fun("with Go."))
让我们运行 this 阅读评估顺序的代码注释:
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()
部分运行s结果为fmt.Print("Go ")
和return funWithGo
,然后fun("with Go.")
运行s,结果是 fmt.Print("have ")
和 return "with Go."
;现在我们有 go funWithGo("with Go.")
。
所以最后的 goroutine 调用是 go funWithGo("with Go.")
这是启动一个新的 goroutine 的调用,所以我们真的不知道它什么时候会 运行。所以下一行有机会运行:fmt.Print("some ")
,那我们就在这里等wg.Wait()
。现在 goroutine 运行s this funWithGo("with Go.")
结果是 fmt.Println("fun", "with Go.")
then wg.Done()
;就是这样。
让我们重写上面的代码,只是将命名函数替换为匿名函数,所以这段代码与上面相同:
例如见:
func have() func(string) {
fmt.Print("Go ") // evaluation order: 1
return funWithGo
}
并剪切此代码 select go have()
中的 have
部分,然后粘贴 select 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 设计者决定在 函数参数 运行-time 时计算函数参数,没有人知道 i
的值;您可以更改代码中的 i
(参见下一个示例)
- 现在让我们做这个闭包:
i := 1
go func() {
time.Sleep(1 * time.Second)
fmt.Println(i) // ?
}()
输出真的是未知,如果main
goroutine早点退出,甚至连运行:唤醒的机会都没有并打印 i
,这是 i
本身可能会更改到那个特定时刻。
- 现在让我们这样解决:
i := 1
go func(i int) {
fmt.Printf("Step 3 i is: %d\n", i) // i = 1
}(i)
这个匿名函数参数是int
类型,是值类型,i
的值是已知的,编译器生成的代码 将值 1
(i
) 压入堆栈,因此此函数将在时间到来时(将来的某个时间)使用值 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
您可能会注意到,1
和 2
的顺序是随机的,您的输出可能会有所不同(请参阅代码注释)。