如何在多个不同的控制台输出 goroutines?

How to make goroutines output in multiple different consoles?

我正在练习使用 goroutines,发现如果两个 goroutines 同时打印,会变得难以阅读。

func main() {
    s1 := rand.NewSource(time.Now().UnixNano())
    r1 := rand.New(s1)
    wg := &sync.WaitGroup{}
    t1 := func(wg *sync.WaitGroup) {
        for i := 0; i < 100; i++ {
            time.Sleep(time.Microsecond * time.Duration(r1.Intn(100)))
            fmt.Println("T1 : ", i)
        }
        wg.Done()
    }
    t2 := func(wg *sync.WaitGroup) {
        for i := 0; i < 100; i++ {
            time.Sleep(time.Microsecond * time.Duration(r1.Intn(100)))
            fmt.Println("T2 : ", i)
        }
        wg.Done()
    }
    wg.Add(2)
    go t1(wg)
    go t2(wg)
    wg.Wait()
}

输出:

T1 :  0
T2 :  0
T2 :  1
T1 :  1
T1 :  2
T2 :  2
T1 :  3
T2 :  3
T1 :  4
T2 :  4
T1 :  5
T2 :  5
T1 :  6
T2 :  6
T2 :  7
T1 :  7
T2 :  8
T1 :  8
T1 :  9
T2 :  9
T2 :  10
T1 :  10
......

有什么办法可以打开多个控制台,让两个goroutine在不同的控制台输出?

  1. 你可以使用一个简单的TCP终端服务器-首先运行这个TCP终端服务器,你不需要关闭它,只要你需要(或只需使用 Netcat 命令: nc -l 8080 然后转到 #2 并写入此 TCP 连接,例如 "127.0.0.1:8080"):
package main

import (
    "io"
    "log"
    "net"
    "os"
)

func main() {
    ln, err := net.Listen("tcp", "127.0.0.1:8080")
    if err != nil {
        log.Fatal(err)
    }
    for {
        w1, err := ln.Accept()
        if err != nil {
            log.Fatal(err)
        }
        io.Copy(os.Stdout, w1)
        w1.Close()
    }
}
  1. 然后将此添加到您的代码中:
    w1, err := net.Dial("tcp", "127.0.0.1:8080")
    if err != nil {
        log.Fatal(err)
    }
    defer w1.Close()
  1. 然后在代码中使用 w1 作为另一个 io.Writer,例如fmt.Fprintln(w1, "T1 : ", i),例子:
package main

import (
    "fmt"
    "log"
    "math/rand"
    "net"
    "sync"
    "time"
)

func main() {
    w1, err := net.Dial("tcp", "127.0.0.1:8080")
    if err != nil {
        log.Fatal(err)
    }
    defer w1.Close()

    // your code:
    s1 := rand.NewSource(time.Now().UnixNano())
    r1 := rand.New(s1)
    wg := &sync.WaitGroup{}
    t1 := func(wg *sync.WaitGroup) {
        for i := 0; i < 100; i++ {
            time.Sleep(time.Microsecond * time.Duration(r1.Intn(100)))
            fmt.Fprintln(w1, "T1 : ", i)
        }
        wg.Done()
    }
    t2 := func(wg *sync.WaitGroup) {
        for i := 0; i < 100; i++ {
            time.Sleep(time.Microsecond * time.Duration(r1.Intn(100)))
            fmt.Println("T2 : ", i)
        }
        wg.Done()
    }
    wg.Add(2)
    go t1(wg)
    go t2(wg)
    wg.Wait()
}

fmt.Print*(...) 函数是 fmt.Fprint*(os.Stdout, ...) 的包装器。如果你想让两个 goroutines 写入不同的作者,你的代码中的第一步是提供他们应该使用的作者:

    t1 := func(wg *sync.WaitGroup, out io.Writer) {
        for i := 0; i < 100; i++ {
            time.Sleep(time.Microsecond * time.Duration(r1.Intn(100)))
            fmt.Fprintln(out, "T1 : ", i)
        }
        wg.Done()
    }

    ...

    // for starters : keep using the same os.Stdout writer
    go t1(wg, os.Stdout)

而不是原始的 io.Writer,您可能想要传递一些您可以登录的东西;标准库有 log.Logger 结构:

    t1 := func(wg *sync.WaitGroup, l *log.Logger) {
        for i := 0; i < 100; i++ {
            time.Sleep(time.Microsecond * time.Duration(r1.Intn(100)))
            l.Println(i)
        }
        wg.Done()
    }

    ...

    // all logging libraries will allow you to specify some option which you can tune :
    l1 := log.New(os.Stdout, "T1 : ", log.Ltime) // will prefix all messages with "[time] T1 : "
    go t1(wg, l1)

    // this allows you to write :
    l2 := log.New(os.Stdout, "T2 : ", log.Ltime) // will prefix all messages with "[time] T2 : "
    go t2(wg, l2)

您可以查看 golang 日志库以了解更多功能——不过,不要花太多时间为快速一次性项目选择日志框架。


现在您已经有了明确的方法将两个不同的编写器(或记录器)传递给您的 goroutines,选择适合您需要写入的内容:

  • 让 goroutine 1 记录到 Stdout,让 goroutine 2 记录到 Stderr
  • 登录到 file1file2——只需 运行 tail -f file2 在一些单独的终端中获得输出的“实时视图”
  • io.Discard/dev/null
  • 的 golang 内置等价物
  • 记录到命名管道、unix 套接字、tcp 连接...