在 Go 语言中文网微信群中,一个群友贴了如下代码:
package main
import (
"fmt"
)
func main() {
done := make(chan int)
go func() {
println("你好")
done <-1
}()
m := <-done
fmt.Println(m)
}
在线运行:https://play.studygolang.com/p/Itvy83BKIIZ (细心的你会发现,“你好”是红色的,“1”是黑色)
他给出的结果是:有时候 “你好” 先输出,有时候 1 先输出,顺序不定。
结果群里其他人一试验,发现顺序一直是固定的,即:
你好
1
原来他使用的是 Goland,如果在终端执行,顺序并不会随机,如果你有 Goland,可以试试。
接着大家讨论,引出了一个小知识点。
println 是 builtin 包提供,语言内置,而 fmt.Println 来自标准库。从 println 函数的注释中还可以了解到更多的信息:
// The println built-in function formats its arguments in an
// implementation-specific way and writes the result to standard error.
// Spaces are always added between arguments and a newline is appended.
// Println is useful for bootstrapping and debugging; it is not guaranteed
// to stay in the language.
fmt.Println 输出到标准输出(os.Stdout),而 println 输出至标准错误(os.Stderr)。而且 println 在参数和换行都会追加空格。
这里要强调一点:println 主要用于程序启动和调试,语言内部实现主要用它。但并不保证未来会一直有这个函数。所以,建议还是老老实实用 fmt 中的打印函数吧。
引用 go101 中的一个问答:内置的`print`和`println`函数与`fmt`和`log`标准库包中相应的打印函数有什么区别?[1]
print
/println
函数总是写入标准错误。fmt
标准包里的打印函数总是写入标准输出。log
标准包里的打印函数会默认写入标准错误,然而也可以通过log.SetOutput
函数来配置。print
/println
函数的调用不能接受数组和结构体参数。print
/println
函数将输出参数的底层值部的地址,而fmt
和log
标准库包中的打印函数将输出参数的字面值。print
/println
函数不会使调用参数引用的值逃逸到堆上,而fmt
和log
标准库包中的的打印函数将使调用参数引用的值逃逸到堆上。String() string
或Error() string
方法,那么fmt
和log
标准库包里的打印函数在打印参数时会调用这两个方法,而内置的print
/println
函数则会忽略参数的这些方法。print
/println
函数不保证在未来的Go版本中继续存在。因为涉及到不同的输出终端,自然涉及到一个问题:缓冲。对 C 有所了解的都知道,基于流的 I/O 提供了 3 种缓冲:
可见,C 中基于流的 I/O,标准输出(stdout)和标准错误(stderr)缓冲方式是不一样的。
如果 Go 中也是如此,那么我们可以改造上面的代码进行试验(更换 println 和 fmt.Println 的位置,以及使用 print 和 fmt.Print),你会发现,在终端输出顺序永远是固定的。也就是说,Go 中的 os.Stdout 并不是行缓冲的。在 golang-nuts 讨论组中有人问了这个问题:os.Stdout is not buffered ?[2],官方给的回答是,os.Stdout 是无缓冲的,因为它的类型实际上是 *os.File
,很显然 *os.File
是无缓冲的。
既然 os.Stdout 和 os.Stderr 都是无缓冲的,那么终端的输出顺序是固定的也就不足为奇了。我们自己实现一个行缓冲的 os.Stdout,来验证它。
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
)
func main() {
writer := NewLineBufferedWriter(os.Stdout)
defer writer.Flush()
done := make(chan int)
go func() {
fmt.Fprint(writer, "你好")
done <- 1
}()
m := <-done
println(m)
}
type LineBufferedWriter struct {
*bufio.Writer
}
func NewLineBufferedWriter(w io.Writer) *LineBufferedWriter {
return &LineBufferedWriter{
Writer: bufio.NewWriter(w),
}
}
func (w *LineBufferedWriter) Write(p []byte) (n int, err error) {
n, err = w.Writer.Write(p)
if err != nil {
return n, err
}
if bytes.Contains(p, []byte{'\n'}) {
w.Flush()
}
return n, err
}
以上代码,因为有行缓冲,永远输出:
1
你好
你扯吧?我这里怎么还是顺序不确定。
好吧,你依然用 Goland 运行的吧。
通过以上的分析,我们可以得出如下结论:
内置的print
和println
函数与fmt
和log
标准库包中相应的打印函数有什么区别?: http://go101.studygolang.com/article/unofficial-faq.html#fmt-print-println
os.Stdout is not buffered ?: https://groups.google.com/forum/#!searchin/golang-nuts/fmt.Print$20buffer%7Csort:date/golang-nuts/PiR4P0joKeo/WM-e2FSaxGcJ
觉得不错,欢迎关注:
点个赞、在看和转发是最大的支持