go os, fmt:文档应提及os.Stdout和fmt.Print*函数是非缓冲的,

new9mtju  于 4个月前  发布在  Go
关注(0)|答案(7)|浏览(87)

你使用的Go版本是什么( go version )?

$ go version
go version go1.13.5 linux/amd64

这个问题在最新版本的发布中是否重现?

是的。

你正在使用什么操作系统和处理器架构( go env )?

go env 输出

$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/terasa/.cache/go-build"
GOENV="/home/terasa/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/terasa/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/go-1.13"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go-1.13/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build094455032=/tmp/go-build -gno-record-gcc-switches"
GOROOT/bin/go version: go version go1.13.5 linux/amd64
GOROOT/bin/go tool compile -V: compile version go1.13.5
uname -sr: Linux 5.3.0-3-amd64
Distributor ID:	Debian
Description:	Debian GNU/Linux bullseye/sid
Release:	testing
Codename:	bullseye
/lib/x86_64-linux-gnu/libc.so.6: GNU C Library (Debian GLIBC 2.29-7) stable release version 2.29.
gdb --version: GNU gdb (Debian 8.3.1-1) 8.3.1

你做了什么?

我只是想打印数组的格式化元素,在这个例子中,我想在我的数组的每个字节前加上一个 0x 。习惯性的C方法大致如下:
print.c :

#include <stdio.h>

int main(int argc, char **argv)
{
    int i;
    unsigned char n[100000] = { 0 };

    for(i=0; i<100000; i++) {
        printf(" 0x%02x", n[i]);
    }
    printf("\n");

    return 0;
}

仅在我机器上运行 time ./print 所测量的性能是

real	0m0.053s
user	0m0.018s
sys	0m0.005s

在go中采用相同的方法导致了灾难性的性能:
print_loop.go :

package main

import "fmt"

func main() {
	var n [100000]byte

	for _, v := range n {
		fmt.Printf(" 0x%02x", v)
	}
	fmt.Printf("\n")
}

我尝试过 go build ,然后用 time ./print_loop 运行生成的二进制文件:

real	0m0.373s
user	0m0.142s
sys	0m0.207s

我意识到可以通过使用 "% x" 作为格式字符串,并将整个数组传递给它,就像 fmt.Printf("0x% x", n) 一样来打印字节数组的所有元素,但这不是我想要的,因为它不会在每个字节前加上 0x
我“碰巧”通过一些谷歌搜索( https://stackoverflow.com/questions/13422381/idiomatically-buffer-os-stdout , https://grokbase.com/t/gg/golang-nuts/158cgb3rfd/go-nuts-os-stdout-is-not-buffered= )发现 go中的 os.Stdout 是无缓冲的,所以我立即尝试使用一个 bufio.Writer ,一切都“正常”。这段代码显然要冗长得多,而且非常重要的是不要忘记刷新缓冲区,因为它显然不会在离开 main (并因此退出)时自动发生:
print_buffer.go :

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	var n [100000]byte

	bufStdout := bufio.NewWriter(os.Stdout)
	defer bufStdout.Flush()

	for _, v := range n {
		fmt.Fprintf(bufStdout, " 0x%02x", v)
	}
	fmt.Fprintf(bufStdout, "\n")
}

这导致:

real	0m0.046s
user	0m0.025s
sys	0m0.000s

问题在于,无论是关于 fmt.Print* 的文档,还是关于 os.Stdout 的文档,都没有提到 I/O 是无缓冲的,而那些使用习惯性的C风格打印格式化输出的人将会大吃一惊。我当然对为什么我的简单的go代码比等效的C代码表现得更差感到困惑。

你期望看到什么?

我希望文档能提到 fmt 函数中I/O操作的缓冲行为,以及对于 os.Stdinos.Stdout 的文档。关于 fmt 的文档也可以链接到 os 的文档,即明确提到使用了 无缓冲os.Stdout

你看到了什么?

什么都没有。

qni6mghb

qni6mghb1#

感谢kohlrabi的撰写。
...我立即尝试使用bufio.Writer,一切都很顺利。这段代码显然更加冗长,最重要的是不要忘记刷新缓冲区,因为它显然不会在离开主函数时自动发生:
刷新行为在bufio中有记录:
在所有数据都被写入后,客户端应该调用Flush方法以确保所有数据都已转发到底层的io.Writer。
我认为这种程度的控制与Go的API设计一致,即通常要求程序员显式地进行操作,并包括诸如defer之类的工具,以使控制更加容易。
我不确定是否同意明确指出*os.File是非缓冲的。并非所有的Go用户都来自C语言,因此他们可能不期望fmt.Printf是缓冲的。
/cc @ianlancetaylor

jw5wzhpr

jw5wzhpr2#

我无法理解为什么在fmt包中添加有关缓冲的注解是有意义的。对于os包,在某个地方简短地写一两句话可能是可以的。

amrnrhlw

amrnrhlw3#

同意,浪费了大量时间在这个调试上...

xxslljrj

xxslljrj4#

它看起来并不像无缓冲的那样工作。我用另一种语言编写的程序中的popen从一个程序中启动Go程序,并看到它随机地放置\n,甚至混合了顺序。浪费了几个小时。我做了Println JSON字符串,它只是被切成两半,所以当我读取这一行时,它不完整,JSON是无效的。更重要的是,如果我做另一个Println,它会在刚刚被毫无理由地分割的JSON的两半之间。

ecfsfe2w

ecfsfe2w5#

无缓冲输出意味着来自多个goroutine的并发输出可以交错。

egdjgwm8

egdjgwm86#

@ianlancetaylor 啊,好的,我以为它意味着它会立即被冲刷掉,防止混乱。
但在我的情况下,我没有做任何并发操作,我的程序很简单。它只是累积JSON字符串,然后在需要时打印出来。在这次编辑后出现了问题:

diff --git a/satellite.go b/satellite.go
index baec58d..479e769 100644
--- a/satellite.go
+++ b/satellite.go
@@ -12,7 +12,7 @@ func main() {
   var err error
   var line []byte
   var temp interface{}
-  var array []interface{}
+  var array [][]byte
   stdin := bufio.NewReader(os.Stdin)
   for {
     if c, err = stdin.ReadByte(); err != nil {
@@ -31,13 +31,10 @@ func main() {
       if err = json.Unmarshal(line, &temp); err != nil {
         panic(err)
       }
-      array = append(array, temp)
+      array = append(array, line)
     case 103:   // g print all and die
       fmt.Println(len(array));
-      for _, temp = range array {
-        if line, err = json.Marshal(temp); err != nil {
-          panic(err)
-        }
+      for _, line = range array {
         fmt.Println(string(line));
       }
       os.Exit(0)

起初我是解析JSON字符串(用于其他需求),将它们存储在 []interface{} 中,但后来我决定不需要存储解析后的它们,开始将字节序列直接存储在 [][]byte 中,就像它们从 stdin := bufio.NewReader(os.Stdin); stdin.ReadLine() 中来的一样。现在每当我运行这个程序时,它会在特定行的同一位置插入 \n 而不是另外三个字节(两个逗号和一个数字)。如果我添加调试打印,它甚至会在中间打印出被破坏的 \n
尝试使用 os.Stdout.Write 代替,但结果完全相同。
我想知道如何按顺序打印到stdout。

**更新:**其他观察结果--被破坏的行缺少前两个字节,而且只有在 array 有多个元素时才发生这种情况。

rmbxnbpk

rmbxnbpk7#

@Nakilon 这个问题最好在论坛上讨论,而不是在这里。请查看 https://golang.org/wiki/Questions 。谢谢。

相关问题