跟着实例学Go语言(四)

本教程全面涵盖了Go语言基础的各个方面。一共80个例子,每个例子对应一个语言特性点,既适合新人快速上手,也适合工作中遇到问题速查知识点。
教程代码示例来自go by example,文字部分来自本人自己的理解。

本文是教程系列的第四部分,共计20个例子、约1.5万字。

系列文章快速跳转:
跟着实例学Go语言(一)
跟着实例学Go语言(二)
跟着实例学Go语言(三)
跟着实例学Go语言(四)

61. Base64 Encoding

Go提供了Base64编解码的功能。Base64是将二进制数据转换成可读字符串的编码方式。例如我们用记事本打开jpg文件,会看到一串乱码,这个就是Base64转换后的字符串。Base64提供了64种不同的字符,6位编码表示一个字符。

package main

import (
    b64 "encoding/base64"
    "fmt"
)

func main() {

    data := "abc123!?$*&()'-=@~"
	// 将data底层的byte slice编码成字符串
    sEnc := b64.StdEncoding.EncodeToString([]byte(data))
    fmt.Println(sEnc)
	// 用Base64解码
    sDec, _ := b64.StdEncoding.DecodeString(sEnc)
    fmt.Println(string(sDec))
    fmt.Println()
	// 用URL兼容的方式编码
    uEnc := b64.URLEncoding.EncodeToString([]byte(data))
    fmt.Println(uEnc)
    // 用URL兼容的方式解码
    uDec, _ := b64.URLEncoding.DecodeString(uEnc)
    fmt.Println(string(uDec))
}
$ go run base64-encoding.go
YWJjMTIzIT8kKiYoKSctPUB+
abc123!?$*&()'-=@~

	

YWJjMTIzIT8kKiYoKSctPUB-
abc123!?$*&()'-=@~

62. Reading Files

下面例子展示了读文件的操作。

package main

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

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
	// 直接从文件读到byte slice
    dat, err := os.ReadFile("/tmp/dat")
    check(err)
    fmt.Print(string(dat))
	// 先打开文件,再对file做更复杂的操作
    f, err := os.Open("/tmp/dat")
    check(err)
	// 自己创建缓冲slice来接收读数据。返回的是读到的字节数
    b1 := make([]byte, 5)
    n1, err := f.Read(b1)
    check(err)
    fmt.Printf("%d bytes: %s\n", n1, string(b1[:n1]))
	// 偏移到下标为6的地方开始读取。两个参数分别代表:基于基准位置的偏移量、基准位置
    o2, err := f.Seek(6, 0)
    check(err)
    b2 := make([]byte, 2)
    n2, err := f.Read(b2)
    check(err)
    fmt.Printf("%d bytes @ %d: ", n2, o2)
    fmt.Printf("%v\n", string(b2[:n2]))

    o3, err := f.Seek(6, 0)
    check(err)
    b3 := make([]byte, 2)
    // ReadAtLeast保证一定读到指定字节数,否则返回错误
    n3, err := io.ReadAtLeast(f, b3, 2)
    check(err)
    fmt.Printf("%d bytes @ %d: %s\n", n3, o3, string(b3))

    _, err = f.Seek(0, 0)
    check(err)
	// bufio包提供了带缓冲的读写并提供一些更简便的操作,这里创建了带缓冲的reader
    r4 := bufio.NewReader(f)
  // 获取后面5个字节,但不实际移动指针,类似于队列中的peek
    b4, err := r4.Peek(5)
    check(err)
    fmt.Printf("5 bytes: %s\n", string(b4))

    f.Close()
}
$ echo "hello" > /tmp/dat
$ echo "go" >>   /tmp/dat
$ go run reading-files.go
hello
go
5 bytes: hello
2 bytes @ 6: go
2 bytes @ 6: go
5 bytes: hello

63. Writing Files

下面例子展示了写文件的操作。

package main

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

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {

    d1 := []byte("hello\ngo\n")
    // 将byte slice写入文件,若文件不存则创建,创建时使用0644代表的权限
    err := os.WriteFile("/tmp/dat1", d1, 0644)
    check(err)
	// 先创建文件,再对file做更复杂的操作
    f, err := os.Create("/tmp/dat2")
    check(err)
	// 将close操作推迟到最后
    defer f.Close()
	// 将自己创建的byte slice写入文件
    d2 := []byte{115, 111, 109, 101, 10}
    n2, err := f.Write(d2)
    check(err)
    fmt.Printf("wrote %d bytes\n", n2)
	// 直接写入一个字符串
    n3, err := f.WriteString("writes\n")
    check(err)
    fmt.Printf("wrote %d bytes\n", n3)
	// 写完后需要调用Sync()才能真正写入硬盘
    f.Sync()

	// 用bufio创建自带缓冲的writer
    w := bufio.NewWriter(f)
    n4, err := w.WriteString("buffered\n")
    check(err)
    fmt.Printf("wrote %d bytes\n", n4)
	// 写完后需要调用Flush(),将缓冲区数据落地
    w.Flush()

}
$ go run writing-files.go 
wrote 5 bytes
wrote 7 bytes
wrote 9 bytes

$ cat /tmp/dat1
hello
go
$ cat /tmp/dat2
some
writes
buffered

64. Line Filters

下面例子展示了从标准输入中读取并解析。类似于Java中的Scanner和Linux下的grep、sed命令。

package main

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

func main() {
	// 从标准输入创建scanner
    scanner := bufio.NewScanner(os.Stdin)
	
    for scanner.Scan() {
		// 默认用空格分隔,每次新读取一段
        ucl := strings.ToUpper(scanner.Text())

        fmt.Println(ucl)
    }

    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "error:", err)
        os.Exit(1)
    }
}
$ echo 'hello'   > /tmp/lines
$ echo 'filter' >> /tmp/lines

$ cat /tmp/lines | go run line-filters.go
HELLO
FILTER

65. File Paths

下面例子展示了对文件路径的各种操作。

package main

import (
    "fmt"
    "path/filepath"
    "strings"
)

func main() {
	// 将不同级别目录名拼接成文件的完整路径
    p := filepath.Join("dir1", "dir2", "filename")
    fmt.Println("p:", p)
	
    fmt.Println(filepath.Join("dir1//", "filename"))
    fmt.Println(filepath.Join("dir1/../dir1", "filename"))
	// 从完整路径获取目录名和文件名
    fmt.Println("Dir(p):", filepath.Dir(p))
    fmt.Println("Base(p):", filepath.Base(p))
	// 是否是绝对路径
    fmt.Println(filepath.IsAbs("dir/file"))
    fmt.Println(filepath.IsAbs("/dir/file"))
	
    filename := "config.json"
	// 获取文件扩展名
    ext := filepath.Ext(filename)
    fmt.Println(ext)
	// 获取去除扩展名后的文件名
    fmt.Println(strings.TrimSuffix(filename, ext))
	// 获取两个路径之间的相对路径
    rel, err := filepath.Rel("a/b", "a/b/t/file")
    if err != nil {
        panic(err)
    }
    fmt.Println(rel)

    rel, err = filepath.Rel("a/b", "a/c/t/file")
    if err != nil {
        panic(err)
    }
    fmt.Println(rel)
}
$ go run file-paths.go
p: dir1/dir2/filename
dir1/filename
dir1/filename
Dir(p): dir1/dir2
Base(p): filename
false
true
.json
config
t/file
../c/t/file

66. Directories

下面例子展示了对目录的相关操作。

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
	// 使用指定的名字和权限创建目录
    err := os.Mkdir("subdir", 0755)
    check(err)
	// 递归删除目录,等效于rm -rf
    defer os.RemoveAll("subdir")

    createEmptyFile := func(name string) {
        d := []byte("")
        // 通过写空数据创建文件,文件不存在就创建
        check(os.WriteFile(name, d, 0644))
    }

    createEmptyFile("subdir/file1")

	// 强制创建目录,若路径上的父级不存在则创建,等效于mkdir -p
    err = os.MkdirAll("subdir/parent/child", 0755)
    check(err)

    createEmptyFile("subdir/parent/file2")
    createEmptyFile("subdir/parent/file3")
    createEmptyFile("subdir/parent/child/file4")

	// 列出指定目录下一级的所有目录和文件
    c, err := os.ReadDir("subdir/parent")
    check(err)

    fmt.Println("Listing subdir/parent")
    for _, entry := range c {
        fmt.Println(" ", entry.Name(), entry.IsDir())
    }

	// 改变当前目录,等效于Linux下的cd命令
    err = os.Chdir("subdir/parent/child")
    check(err)
	// 对当前目录执行ReadDir
    c, err = os.ReadDir(".")
    check(err)

    fmt.Println("Listing subdir/parent/child")
    for _, entry := range c {
        fmt.Println(" ", entry.Name(), entry.IsDir())
    }

    err = os.Chdir("../../..")
    check(err)

    fmt.Println("Visiting subdir")
	// 递归遍历指定目录下的所有子目录和文件,方式为深度优先遍历
    err = filepath.Walk("subdir", visit)
}

func visit(p string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }
    fmt.Println(" ", p, info.IsDir())
    return nil
}
$ go run directories.go
Listing subdir/parent
  child true
  file2 false
  file3 false
Listing subdir/parent/child
  file4 false
Visiting subdir
  subdir true
  subdir/file1 false
  subdir/parent true
  subdir/parent/child true
  subdir/parent/child/file4 false
  subdir/parent/file2 false
  subdir/parent/file3 false

67. Temporary Files and Directories

下面例子展示了对临时文件和目录的操作。

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {

    f, err := os.CreateTemp("", "sample")
    check(err)

    fmt.Println("Temp file name:", f.Name())
	// 操作系统会在程序结束后一段时间自动清理临时文件,不过最好还是自己做清理
    defer os.Remove(f.Name())

    _, err = f.Write([]byte{1, 2, 3, 4})
    check(err)

    dname, err := os.MkdirTemp("", "sampledir")
    check(err)
    fmt.Println("Temp dir name:", dname)

    defer os.RemoveAll(dname)

    fname := filepath.Join(dname, "file1")
    err = os.WriteFile(fname, []byte{1, 2}, 0666)
    check(err)
}
$ go run temporary-files-and-directories.go
Temp file name: /tmp/sample610887201
Temp dir name: /tmp/sampledir898854668

68. Embed Directive

embed编译指令可以在程序运行时从指定的文件获取数据到程序变量。这种编程风格有点类似于Srping中的依赖注入。

package main

import (
    "embed"
)

// 使用//go:embed指定从文件读取内容到变量fileString、fileByte
//go:embed folder/single_file.txt
var fileString string

//go:embed folder/single_file.txt
var fileByte []byte

// 使用//go:embed也可以打包多个文件到embed.FS类型变量,方便后续操作 
//go:embed folder/single_file.txt
//go:embed folder/*.hash
var folder embed.FS

func main() {

    print(fileString)
    print(string(fileByte))

    content1, _ := folder.ReadFile("folder/file1.hash")
    print(string(content1))

    content2, _ := folder.ReadFile("folder/file2.hash")
    print(string(content2))
}
$ mkdir -p folder
$ echo "hello go" > folder/single_file.txt
$ echo "123" > folder/file1.hash
$ echo "456" > folder/file2.hash

	

$ go run embed-directive.go
hello go
hello go
123
456

69. Testing and Benchmarking

Go对于单元测试和基准测试提供了原生的支持。仅需以指定的格式命名函数,通过正则匹配会被自动识别为单元测试或基准测试用例,再用go test命令启动测试。

package main

import (
    "fmt"
    "testing"
)

func IntMin(a, b int) int {
    if a < b {
        return a
    }
    return b
}

// 测试用例需要以Test开头
func TestIntMinBasic(t *testing.T) {
    ans := IntMin(2, -2)
    if ans != -2 {

        t.Errorf("IntMin(2, -2) = %d; want -2", ans)
    }
}

func TestIntMinTableDriven(t *testing.T) {
    var tests = []struct {
        a, b int
        want int
    }{
        {0, 1, 0},
        {1, 0, 0},
        {2, -2, -2},
        {0, -1, -1},
        {-1, 0, -1},
    }

    for _, tt := range tests {

        testname := fmt.Sprintf("%d,%d", tt.a, tt.b)
        // 用Run启动子测试用例,每个用例用数据表中获取输入
        t.Run(testname, func(t *testing.T) {
            ans := IntMin(tt.a, tt.b)
            if ans != tt.want {
                t.Errorf("got %d, want %d", ans, tt.want)
            }
        })
    }
}

// 基准测试需要以Benchmark开头
func BenchmarkIntMin(b *testing.B) {
	// b.N是执行次数,由底层自动决定,以产生稳定精确的性能统计
    for i := 0; i < b.N; i++ {
        IntMin(1, 2)
    }
}
// 执行所有测试,显示详细信息,包括子测试
$ go test -v
== RUN   TestIntMinBasic
--- PASS: TestIntMinBasic (0.00s)
=== RUN   TestIntMinTableDriven
=== RUN   TestIntMinTableDriven/0,1
=== RUN   TestIntMinTableDriven/1,0
=== RUN   TestIntMinTableDriven/2,-2
=== RUN   TestIntMinTableDriven/0,-1
=== RUN   TestIntMinTableDriven/-1,0
--- PASS: TestIntMinTableDriven (0.00s)
    --- PASS: TestIntMinTableDriven/0,1 (0.00s)
    --- PASS: TestIntMinTableDriven/1,0 (0.00s)
    --- PASS: TestIntMinTableDriven/2,-2 (0.00s)
    --- PASS: TestIntMinTableDriven/0,-1 (0.00s)
    --- PASS: TestIntMinTableDriven/-1,0 (0.00s)
PASS
ok      examples/testing-and-benchmarking    0.023s

// 执行基准测试
$ go test -bench=.
goos: darwin
goarch: arm64
pkg: examples/testing
BenchmarkIntMin-8 1000000000 0.3136 ns/op
PASS
ok      examples/testing-and-benchmarking    0.351s

70. Command-Line Arguments

通过Go提供的命令行api,可以方便地把Go编译出来的程序用作命令行工具。下面例子展示了Go程序读取命令行参数并处理的过程。

package main

import (
    "fmt"
    "os"
)

func main() {
	// os.Args包含所有参数,当前文件编译生成的可执行文件为第一个参数
    argsWithProg := os.Args
    argsWithoutProg := os.Args[1:]

    arg := os.Args[3]

    fmt.Println(argsWithProg)
    fmt.Println(argsWithoutProg)
    fmt.Println(arg)
}
$ go build command-line-arguments.go
$ ./command-line-arguments a b c d
[./command-line-arguments a b c d]       
[a b c d]
c

71. Command-Line Flags

Go支持命令行标志,为命令行提供了不同的选项。例如grep -c中的-c就是一种命令行标志。

package main

import (
    "flag"
    "fmt"
)

func main() {
	// 新定义word作为命令行标志,三个参数为:标志名、默认值、注释
    wordPtr := flag.String("word", "foo", "a string")

    numbPtr := flag.Int("numb", 42, "an int")
    forkPtr := flag.Bool("fork", false, "a bool")

    var svar string
    flag.StringVar(&svar, "svar", "bar", "a string var")

    flag.Parse()

    fmt.Println("word:", *wordPtr)
    fmt.Println("numb:", *numbPtr)
    fmt.Println("fork:", *forkPtr)
    fmt.Println("svar:", svar)
    fmt.Println("tail:", flag.Args())
}
 go build command-line-flags.go


	

$ ./command-line-flags -word=opt -numb=7 -fork -svar=flag
word: opt
numb: 7
fork: true
svar: flag
tail: []


	

$ ./command-line-flags -word=opt
word: opt
numb: 42
fork: false
svar: bar
tail: []


	

$ ./command-line-flags -word=opt a1 a2 a3
word: opt
...
tail: [a1 a2 a3]

	

$ ./command-line-flags -word=opt a1 a2 a3 -numb=7
word: opt
numb: 42
fork: false
svar: bar
tail: [a1 a2 a3 -numb=7]

$ ./command-line-flags -h
Usage of ./command-line-flags:
  -fork=false: a bool
  -numb=42: an int
  -svar="bar": a string var
  -word="foo": a string


$ ./command-line-flags -wat
flag provided but not defined: -wat
Usage of ./command-line-flags:
...

72. Command-Line Subcommands

Go支持命令行子命令。例如go build和run就是go下面的两个子命令。

package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {

    fooCmd := flag.NewFlagSet("foo", flag.ExitOnError)
    fooEnable := fooCmd.Bool("enable", false, "enable")
    fooName := fooCmd.String("name", "", "name")

    barCmd := flag.NewFlagSet("bar", flag.ExitOnError)
    barLevel := barCmd.Int("level", 0, "level")

    if len(os.Args) < 2 {
        fmt.Println("expected 'foo' or 'bar' subcommands")
        os.Exit(1)
    }

    switch os.Args[1] {

    case "foo":
        fooCmd.Parse(os.Args[2:])
        fmt.Println("subcommand 'foo'")
        fmt.Println("  enable:", *fooEnable)
        fmt.Println("  name:", *fooName)
        fmt.Println("  tail:", fooCmd.Args())
    case "bar":
        barCmd.Parse(os.Args[2:])
        fmt.Println("subcommand 'bar'")
        fmt.Println("  level:", *barLevel)
        fmt.Println("  tail:", barCmd.Args())
    default:
        fmt.Println("expected 'foo' or 'bar' subcommands")
        os.Exit(1)
    }
}
$ go build command-line-subcommands.go 


	

$ ./command-line-subcommands foo -enable -name=joe a1 a2
subcommand 'foo'
  enable: true
  name: joe
  tail: [a1 a2]


	

$ ./command-line-subcommands bar -level 8 a1
subcommand 'bar'
  level: 8
  tail: [a1]


	

$ ./command-line-subcommands bar -enable a1
flag provided but not defined: -enable
Usage of bar:
  -level int
        level

73. Environment Variables

下面例子展示了环境变量的设置和读取。

package main

import (
    "fmt"
    "os"
    "strings"
)

func main() {
	// 环境变量为进程级别,进程退出后设置的变量消失
    os.Setenv("FOO", "1")
    fmt.Println("FOO:", os.Getenv("FOO"))
    fmt.Println("BAR:", os.Getenv("BAR"))

    fmt.Println()
    // 读取到的变量列表与具体机器有关
    for _, e := range os.Environ() {
        pair := strings.SplitN(e, "=", 2)
        fmt.Println(pair[0])
    }
}
$ go run environment-variables.go
FOO: 1
BAR: 


	

TERM_PROGRAM
PATH
SHELL
...
FOO


	

$ BAR=2 go run environment-variables.go
FOO: 1
BAR: 2
...

74. HTTP Client

Go提供了方便的Http请求和处理的工具。下面是HTTP客户端的例子。

package main

import (
    "bufio"
    "fmt"
    "net/http"
)

func main() {
	// 仅一句Get就能访问http url
    resp, err := http.Get("https://gobyexample.com")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    fmt.Println("Response status:", resp.Status)

    scanner := bufio.NewScanner(resp.Body)
    for i := 0; scanner.Scan() && i < 5; i++ {
        fmt.Println(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        panic(err)
    }
}
$ go run http-clients.go
Response status: 200 OK
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Go by Example</title>

75. HTTP Server

下面是HTTP服务端的例子。仅需HandleFunc和ListenAndServe两句代码就能实现一个简单的服务端,非常方便。

package main

import (
    "fmt"
    "net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {

    fmt.Fprintf(w, "hello\n")
}

func headers(w http.ResponseWriter, req *http.Request) {

    for name, headers := range req.Header {
        for _, h := range headers {
            fmt.Fprintf(w, "%v: %v\n", name, h)
        }
    }
}

func main() {
	
    http.HandleFunc("/hello", hello)
    http.HandleFunc("/headers", headers)

    http.ListenAndServe(":8090", nil)
}
 	

$ go run http-servers.go &

$ curl localhost:8090/hello
hello

76. Context

下面例子展示了通过HTTP请求上下文处理请求取消的操作。

package main

import (
    "fmt"
    "net/http"
    "time"
)

func hello(w http.ResponseWriter, req *http.Request) {

    ctx := req.Context()
    fmt.Println("server: hello handler started")
    defer fmt.Println("server: hello handler ended")

    select {
    case <-time.After(10 * time.Second):
        fmt.Fprintf(w, "hello\n")
    // 若在10秒内断开请求连接,则执行下面逻辑
    case <-ctx.Done():

        err := ctx.Err()
        fmt.Println("server:", err)
        internalError := http.StatusInternalServerError
        http.Error(w, err.Error(), internalError)
    }
}

func main() {

    http.HandleFunc("/hello", hello)
    http.ListenAndServe(":8090", nil)
}
$ go run context-in-http-servers.go &

$ curl localhost:8090/hello
server: hello handler started
// 这里模拟在接到回应前,断开请求连接
^C
server: context canceled
server: hello handler ended

77. Spawning Processes

下面例子展示了在Go程序中调用其他命令,如date,并获取返回值。

package main

import (
    "fmt"
    "io"
    "os/exec"
)

func main() {

    dateCmd := exec.Command("date")

    dateOut, err := dateCmd.Output()
    if err != nil {
        panic(err)
    }
    fmt.Println("> date")
    fmt.Println(string(dateOut))

    _, err = exec.Command("date", "-x").Output()
    if err != nil {
        switch e := err.(type) {
        case *exec.Error:
            fmt.Println("failed executing:", err)
        case *exec.ExitError:
            fmt.Println("command exit rc =", e.ExitCode())
        default:
            panic(err)
        }
    }

    grepCmd := exec.Command("grep", "hello")

    grepIn, _ := grepCmd.StdinPipe()
    grepOut, _ := grepCmd.StdoutPipe()
    grepCmd.Start()
    grepIn.Write([]byte("hello grep\ngoodbye grep"))
    grepIn.Close()
    grepBytes, _ := io.ReadAll(grepOut)
    grepCmd.Wait()

    fmt.Println("> grep hello")
    fmt.Println(string(grepBytes))

    lsCmd := exec.Command("bash", "-c", "ls -a -l -h")
    lsOut, err := lsCmd.Output()
    if err != nil {
        panic(err)
    }
    fmt.Println("> ls -a -l -h")
    fmt.Println(string(lsOut))
}
$ go run spawning-processes.go 
> date
Thu 05 May 2022 10:10:12 PM PDT
	

command exited with rc = 1
> grep hello
hello grep

	

> ls -a -l -h
drwxr-xr-x  4 mark 136B Oct 3 16:29 .
drwxr-xr-x 91 mark 3.0K Oct 3 12:50 ..
-rw-r--r--  1 mark 1.3K Oct 3 16:28 spawning-processes.go

78. Exec’ing Processes

下面展示了从Go程序启动一个新的进程。与上一个例子不同,这里启动新进程后,原Go程序会主动退出。

package main

import (
    "os"
    "os/exec"
    "syscall"
)

func main() {

    binary, lookErr := exec.LookPath("ls")
    if lookErr != nil {
        panic(lookErr)
    }

    args := []string{"ls", "-a", "-l", "-h"}

    env := os.Environ()

    execErr := syscall.Exec(binary, args, env)
    if execErr != nil {
        panic(execErr)
    }
}
$ go run execing-processes.go
total 16
drwxr-xr-x  4 mark 136B Oct 3 16:29 .
drwxr-xr-x 91 mark 3.0K Oct 3 12:50 ..
-rw-r--r--  1 mark 1.3K Oct 3 16:28 execing-processes.go

79. Signals

下面例子展示了信号量的使用。通过监听中止信号量,在外部强行中断程序后,还能执行一段结束代码。

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {

    sigs := make(chan os.Signal, 1)
	// 监听程序中止的信号量,并存入通道sigs
    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    done := make(chan bool, 1)

    go func() {
		// 从sigs通道读取信号量并打印
        sig := <-sigs
        fmt.Println()
        fmt.Println(sig)
        done <- true
    }()

    fmt.Println("awaiting signal")
    <-done
    fmt.Println("exiting")
}
$ go run signals.go
awaiting signal
^C
interrupt
exiting

80. Exit

下面例子展示了以指定退出码主动退出进程。主动退出时不会指定defer语句。

package main

import (
    "fmt"
    "os"
)

func main() {

    defer fmt.Println("!")

    os.Exit(3)
}
$ go run exit.go
exit status 3

$ go build exit.go
$ ./exit
$ echo $?
3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值