标签 Package 下的文章

探索Go gcflags的使用模式与完整参数选项列表

本文永久链接 – https://tonybai.com/2025/01/22/gcflags-options-list-and-usage

Go build是Go开发中不可或缺的构建工具,其中-gcflags参数为开发者提供了向编译器传递额外选项的能力。然而,关于-gcflags的完整参数选项和使用模式,官方文档多有局限,很多开发者对此了解不深。本文将系统性地解析-gcflags的完整参数来源以及其结合包模式(package pattern)的使用方法,供大家参考。

注:本文主要以-gcflags为例,其实go build的-ldflags参数与-gcflags在使用方法上如出一辙,唯一不同的是ldflags是将参数传递给go链接器。

gcflags是Go构建工具的一个标志,用于向Go编译器 (go tool compile) 传递额外的编译参数。通过它,开发者可以调整编译行为,例如禁用优化、生成调试信息或输出反汇编代码等。

Go build文档中关于-gcflags的说明很短小精悍:

$go help build
... ...
    -gcflags '[pattern=]arg list'
        arguments to pass on each go tool compile invocation.
    -ldflags '[pattern=]arg list'
        arguments to pass on each go tool link invocation.
... ...

The -asmflags, -gccgoflags, -gcflags, and -ldflags flags accept a space-separated list of arguments to pass to an underlying tool during the build. To embed spaces in an element in the list, surround it with either single or double quotes. The argument list may be preceded by a package pattern and an equal sign, which restricts the use of that argument list to the building of packages matching that pattern (see 'go help packages' for a description of package patterns). Without a pattern, the argument list applies only to the packages named on the command line. The flags may be repeated with different patterns in order to specify different arguments for different sets of packages. If a package matches patterns given in multiple flags, the latest match on the command line wins. For example, 'go build -gcflags=-S fmt' prints the disassembly only for package fmt, while 'go build -gcflags=all=-S fmt' prints the disassembly for fmt and all its dependencies.

... ...

多数Go初学者初次看到上述关于gcflags的说明,都无法知道到底有哪些arg可用以及究竟如何使用gcflags,而Go cmd文档中关于gcflags的内容也仅限于上述这些。

我将大家遇到的主要问题总结为下面两条:

  • gcflags的完整参数选项列表在哪里可以找到?
  • gcflags的使用模式,尤其是其中的package pattern应该如何正确使用?

如果你能正确回答上述两个问题,那你就基本掌握了gcflags的使用,大可不必继续往下看了

否则,我们就一起分别看一下这两个问题该如何解答。

在哪里能查找到gcflags可用的全部参数选项呢?go help build不行,go command的web文档中没有!甚至Go tool compile的web文档中列举的gcflag的参数列表也是不全的(或者说是文档没有及时同步最新的参数列表变化),也许我们应该提一个issue给Go团队^_^。

远在天边近在眼前!下面命令可以让-gcflag可用的参数选项完整列表尽收眼底:

$go tool compile -h
usage: compile [options] file.go...
  -%    debug non-static initializers
  -+    compiling runtime
  -B    disable bounds checking
  -C    disable printing of columns in error messages
  -D path
        set relative path for local imports
  -E    debug symbol export
  -I directory
        add directory to import search path
  -K    debug missing line numbers
  -L    also show actual source file names in error messages for positions affected by //line directives
  -N    disable optimizations
  -S    print assembly listing
  -V    print version and exit
  -W    debug parse tree after type checking
  -asan
        build code compatible with C/C++ address sanitizer
  -asmhdr file
        write assembly header to file
... ...

同样,如果你要查看-ldflags的完整参数选项列表,你可以使用下面命令:

$go tool link -h
usage: link [options] main.o
  -B note
        add an ELF NT_GNU_BUILD_ID note when using ELF; use "gobuildid" to generate it from the Go build ID
  -E entry
        set entry symbol name
  -H type
        set header type
  -I linker
        use linker as ELF dynamic linker
  -L directory
        add specified directory to library path
  -R quantum
        set address rounding quantum (default -1)
  -T int
        set the start address of text symbols (default -1)
  -V    print version and exit
  -X definition
        add string value definition of the form importpath.name=value
  -a    no-op (deprecated)
  -asan
        enable ASan interface
... ...

到这里,我们得到了第一个问题的答案。

接下来,我们再来看第二个问题:-gcflags的使用模式。

根据go help build的输出,我们知道-gcflags的使用形式如下:

-gcflags '[pattern=]arg list'

其中:

  • [pattern=](可选):包模式(package pattern),用于作用范围控制,即限定参数仅应用于特定的包。如果省略此部分,则参数仅适用于命令行中指定的包。
  • arg list:参数选项列表,多个参数以空格分隔。

对包模式有很好地理解并非是使用好gcflags的必要条件。但在一些复杂项目中,我们可能会通过包模式精确控制调试和优化,在这种情况下,对包模式有深入理解还是大有裨益的。

包模式是一种通过匹配规则指定目标包的方式,常见的包模式有几下几种:

  • ./…:匹配当前目录及其所有子目录中的包。
  • /DIR/…:匹配/DIR及其子目录中的包。
  • cmd/…:匹配Go仓库中cmd目录下的所有命令包。
  • github.com/user/repo/…:匹配该github仓库中的所有包。
  • all:GOPATH模式下,匹配的是所有GOPATH路径中的包,Go module模式下,all匹配主模块及其所有依赖的包(包括测试依赖)。
  • std:仅匹配标准库包。
  • cmd:匹配Go仓库中的Go命令及其内部包(internal)。

基于上述关于gcflags使用形式以及包模式的说明,我们举几个示例来直观理解一下gcflags的用法:

  • 对单个包设置参数
$go build -gcflags=-S fmt

上述命令中的参数-S仅作用于fmt包,显示其反汇编代码。

  • 对特定模式(比如all/std等)的包设置参数
$go build -gcflags='all=-N -l'

在Go module模式下,参数-N和-l应用于当前主模块所有包及其依赖,禁用优化和内联。

  • 对不同包模式设置不同参数
$go build -gcflags='fmt=-S' -gcflags='net/http=-N'

Go build命令行中可以多次使用-gcflags,上述命令中的第一个gcflags对fmt包启用反汇编输出(-S)。第二个gcflags对net/http包禁用优化(-N)。

  • 模式的优先级
$go build -gcflags='all=-N' -gcflags='fmt=-S'

像上面命令中,两个gcflags都匹配了fmt包,或者说两个gcflags的作用范围都包含了fmt包,这种情况下哪些参数会对fmt包生效呢?Go规定:当一个包匹配多个模式时,以最后一个匹配的参数为准。在这个例子中,fmt包将只应用-S参数,而其他包应用-N参数。

到这里,我们完成了对两个关于gcflags问题的回答!

最后小结一下:

  • gcflags(以及-ldflags)是Go构建工具中的重要选项,能极大提升调试和优化效率。
  • gcflags的完整的参数选项需通过底层工具获取,即go tool compile -h和go tool link -h。
  • 对包模式的灵活使用能够精确控制gcflags参数的作用范围,为复杂项目提供了更大的自由度。

通过本篇文章,希望你能掌握查看gcflags完整参数列表的方法以及gcflags的使用模式,并在构建和调试Go项目时能更加得心应手。


Gopher部落知识星球在2025年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。并且,2025年将在星球首发“Go陷阱与缺陷”和“Go原理课”专栏!此外,我们还会加强星友之间的交流和互动。欢迎大家踊跃提问,分享心得,讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾
。让我相聚在Gopher部落,享受coding的快乐! 欢迎大家踊跃加入!

img{512x368}
img{512x368}

img{512x368}
img{512x368}

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻) – https://gopherdaily.tonybai.com

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx
  • 微博2:https://weibo.com/u/6484441286
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • Gopher Daily归档 – https://github.com/bigwhite/gopherdaily
  • Gopher Daily Feed订阅 – https://gopherdaily.tonybai.com/feed

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

Go包构建:专家也未必了解的文件选择细节

本文永久链接 – https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package

在Go语言开发中,包(package)是代码组织的基本单位,也是基本的构建单元。Go编译器会将每个包构建成一个目标文件(.a),然后通过链接器将这些目标文件链接在一起,形成最终的可执行程序。

尽管Go包的构建过程看似简单,但实际上蕴含着许多值得深入了解的细节。例如,当我们执行go build命令时,Go编译器是如何选择需要编译的源文件的?你可能会回答:“不就是通过文件名中的ARCH和OS标识以及构建约束(build constraints)来选择的吗?” 虽然你的答案并没有错,但如果我进一步提出以下问题,你是否还能给出确切的答案呢?

假设一个Go源文件使用了如下的构建约束:

//go:build unix

package foo
// ... ...

在执行GOOS=android go build时,这个文件是否会被编译?如果执行的是GOOS=aix go build呢?而“unix”究竟包含了哪些操作系统?

再进一步,当一个源文件的文件名中包含ARCH和操作系统标识,并且文件内容中也使用了构建约束时,Go编译器会如何处理这些信息的优先级?

即使是经验丰富的Go专家,对于上述在包构建过程中涉及的文件选择细节,可能也只能给出模糊的答案。

在实际开发中,我们常常需要针对不同操作系统和架构编写特定的代码,这意味着灵活性与复杂性并存。Go的构建约束和文件名约定虽然为我们提供了灵活性,但也带来了额外的复杂性。理解这些规则不仅有助于优化构建过程,还能有效避免潜在的错误和不必要的麻烦。

在这篇文章中,我将与大家探讨Go包构建过程中源文件选择的细节,包括文件名中ARCH和os标识约定和构建约束的作用,以及二者的优先级处理问题。希望通过这些内容,帮助开发者更好地掌握Go语言的构建机制,从而提高开发效率。

为了更好地说明Go包构建时的文件选择逻辑,我们先从Go包构建的一些“表象”说起。

注:在本文中,我们将使用Go 1.17引入的新版build constraints写法://go:build ,之前的// +build aix darwin dragonfly freebsd js,wasm …写法已经不再被推荐使用。如果你想对旧版build constraints写法有一个全面了解以便与新写法对比,推荐阅读我的《Go语言精进之路:从新手到高手的编程思想、方法和技巧》第2册

1. 表象

在Go工程中,通常一个目录对应一个Go包,每个Go包下可以存在多个以.go为后缀的Go源文件,这些源文件只能具有唯一的包名(测试源文件除外),以标准库fmt包为例,它的目录下的源文件列表如下(以Go 1.23.0源码为例):

$ls $GOROOT/src/fmt
doc.go              export_test.go          print.go            stringer_example_test.go
errors.go           fmt_test.go         scan.go             stringer_test.go
errors_test.go          format.go           scan_test.go
example_test.go         gostringer_example_test.go  state_test.go

在这些文件中,哪些最终进入到了fmt包的目标文件(fmt.a)中呢?贴心的Go工具链为我们提供了查看方法:

$go list -f '{{.GoFiles}}' fmt
[doc.go errors.go format.go print.go scan.go]

对于独立于目标ARCH和OS的fmt包来说,其Go源文件的选择似乎要简单一些。我们看到,除了包测试文件(xxx_test.go),其他文件都被编译到了最终的fmt包中。

我们再来看一个与目标ARCH和OS相关性较高的net包。除去子目录,这个包目录下的Go源文件数量大约有220多个,但在macOS/amd64下通过go list查看最终进入net包目标文件的文件,大约只有几十个:

$go list -f '{{.GoFiles}}' net
[addrselect.go cgo_darwin.go cgo_unix.go cgo_unix_syscall.go conf.go dial.go dnsclient.go dnsclient_unix.go dnsconfig.go dnsconfig_unix.go error_posix.go error_unix.go fd_posix.go fd_unix.go file.go file_unix.go hook.go hook_unix.go hosts.go interface.go interface_bsd.go interface_darwin.go ip.go iprawsock.go iprawsock_posix.go ipsock.go ipsock_posix.go lookup.go lookup_unix.go mac.go mptcpsock_stub.go net.go netcgo_off.go netgo_off.go nss.go parse.go pipe.go port.go port_unix.go rawconn.go rlimit_unix.go sendfile_unix_alt.go sock_bsd.go sock_posix.go sockaddr_posix.go sockopt_bsd.go sockopt_posix.go sockoptip_bsdvar.go sockoptip_posix.go splice_stub.go sys_cloexec.go tcpsock.go tcpsock_posix.go tcpsock_unix.go tcpsockopt_darwin.go tcpsockopt_posix.go udpsock.go udpsock_posix.go unixsock.go unixsock_posix.go unixsock_readmsg_cloexec.go writev_unix.go]

接下来,我们跳出Go标准库,来看一个自定义的示例:

$tree -F buildconstraints/demo1
buildconstraints/demo1
├── foo/
│   ├── f1_android.go
│   ├── f2_linux.go
│   └── f3_darwin.go
└── go.mod

// buildconstraints/demo1/foo/f1_android.go 

//go:build linux

package foo

func F1() {
}

// buildconstraints/demo1/foo/f2_linux.go
//go:build android

package foo

func F2() {
}

// buildconstraints/demo1/foo/f3_darwin.go
//go:build android

package foo

func F3() {
}

在GOOS=android下构建buildconstraints/demo1/foo这个包,哪些文件会被选出来呢,看下面输出结果:

$GOOS=android go list -f '{{.GoFiles}}' github.com/bigwhite/demo1/foo
[f1_android.go f2_linux.go]

如果说前两个示例还好理解,那这第三个示例很可能会让很多开发者觉得有些“发蒙”。 别急,上面三个示例都是表象,接下来,我们就来仔细探索一下Go构建时的文件选择机制。

2. 文件选择机制

Go包构建时选择源文件的机制还是蛮繁琐的,我们需要从源码入手梳理出其主要逻辑,在Go 1.23版本中,Go包构建过程源文件选择逻辑的代码位于\$GOROOT/src/go/build/build.go中,这个源文件有2k多行,不过不用担心,我这里会替你把主要调用逻辑梳理为下图:

函数Import调用Default.Import去获取包的详细信息,信息用build.Package结构表示:

// $GOROOT/src/go/build/build.go
// A Package describes the Go package found in a directory.
  type Package struct {
      Dir           string   // directory containing package sources
      Name          string   // package name
      ImportComment string   // path in import comment on package statement
      Doc           string   // documentation synopsis
      ImportPath    string   // import path of package ("" if unknown)
      Root          string   // root of Go tree where this package lives
      SrcRoot       string   // package source root directory ("" if unknown)
      PkgRoot       string   // package install root directory ("" if unknown)
      PkgTargetRoot string   // architecture dependent install root directory ("" if unknown)
      BinDir        string   // command install directory ("" if unknown)
      Goroot        bool     // package found in Go root
      PkgObj        string   // installed .a file
      AllTags       []string // tags that can influence file selection in this directory
      ConflictDir   string   // this directory shadows Dir in $GOPATH
      BinaryOnly    bool     // cannot be rebuilt from source (has //go:binary-only-package comment)

      // Source files
      GoFiles           []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
      ... ...

其中的GoFiles就是参与Go包编译的源文件列表。

Default是默认的上下文信息,包括构建所需的默认goenv中几个环境变量,比如GOARCH、GOOS等的值:

// Default is the default Context for builds.
// It uses the GOARCH, GOOS, GOROOT, and GOPATH environment variables
// if set, or else the compiled code's GOARCH, GOOS, and GOROOT.
var Default Context = defaultContext()

Context的Import方法代码行数很多,对于要了解文件选择细节的我们来说,其中最重要的调用是Context的matchFile方法。

matchFile正是那个用于确定某个Go源文件是否应该被选入最终包文件中的方法。它内部的逻辑可以分为两个主要步骤。

第一步是调用Context的goodOSArchFile方法对Go源文件的名字进行判定,goodOSArchFile方法的判定也有两个子步骤:

  • 判断名字中的OS和ARCH是否在Go支持的OS和ARCH列表中

当前Go支持的OS和ARCH在syslist.go文件中有定义:

// $GOROOT/src/go/build/syslist.go

// knownArch is the list of past, present, and future known GOARCH values.
// Do not remove from this list, as it is used for filename matching.
var knownArch = map[string]bool{
    "386":         true,
    "amd64":       true,
    "amd64p32":    true,
    "arm":         true,
    "armbe":       true,
    "arm64":       true,
    "arm64be":     true,
    "loong64":     true,
    "mips":        true,
    "mipsle":      true,
    "mips64":      true,
    "mips64le":    true,
    "mips64p32":   true,
    "mips64p32le": true,
    "ppc":         true,
    "ppc64":       true,
    "ppc64le":     true,
    "riscv":       true,
    "riscv64":     true,
    "s390":        true,
    "s390x":       true,
    "sparc":       true,
    "sparc64":     true,
    "wasm":        true,
}

// knownOS is the list of past, present, and future known GOOS values.
// Do not remove from this list, as it is used for filename matching.
// If you add an entry to this list, look at unixOS, below.
var knownOS = map[string]bool{
    "aix":       true,
    "android":   true,
    "darwin":    true,
    "dragonfly": true,
    "freebsd":   true,
    "hurd":      true,
    "illumos":   true,
    "ios":       true,
    "js":        true,
    "linux":     true,
    "nacl":      true,
    "netbsd":    true,
    "openbsd":   true,
    "plan9":     true,
    "solaris":   true,
    "wasip1":    true,
    "windows":   true,
    "zos":       true,
}

我们也可以通过下面命令查看:

$go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/amd64
darwin/arm64
dragonfly/amd64
freebsd/386
freebsd/amd64
freebsd/arm
freebsd/arm64
freebsd/riscv64
illumos/amd64
ios/amd64
ios/arm64
js/wasm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/loong64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/riscv64
linux/s390x
netbsd/386
netbsd/amd64
netbsd/arm
netbsd/arm64
openbsd/386
openbsd/amd64
openbsd/arm
openbsd/arm64
openbsd/ppc64
openbsd/riscv64
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
wasip1/wasm
windows/386
windows/amd64
windows/arm
windows/arm64

注:像sock_bsd.go、sock_posix.go这样的Go源文件,虽然它们的文件名中包含posix、bsd等字样,但这些文件实际上只是普通的Go源文件。其文件名本身并不会影响Go包在构建时选择文件的结果。

  • 调用matchTag来判定该Go源文件名字中的OS和ARCH是否与当前上下文信息中的OS和ARCH匹配

Go支持的源文件名组成格式如下:

  //  name_$(GOOS).*
  //  name_$(GOARCH).*
  //  name_$(GOOS)_$(GOARCH).*
  //  name_$(GOOS)_test.*
  //  name_$(GOARCH)_test.*
  //  name_$(GOOS)_$(GOARCH)_test.*

不过这里有三个例外,即:

如果上下文中的GOOS=android,那么文件名字中OS值为linux的Go源文件也算是匹配的;

如果上下文中的GOOS=illumos,那么文件名字中OS值为solaris的Go源文件也算是匹配的;

如果上下文中的GOOS=ios,那么文件名字中OS值为darwin的Go源文件也算是匹配的。

还有一个特殊处理,那就是当文件名字中OS值为unix时,该源文件可以匹配以下上下文中GOOS的值:

// $GOROOT/src/go/build/syslist.go

// unixOS is the set of GOOS values matched by the "unix" build tag.
// This is not used for filename matching.
// This list also appears in cmd/dist/build.go and
// cmd/go/internal/imports/build.go.
var unixOS = map[string]bool{
    "aix":       true,
    "android":   true,
    "darwin":    true,
    "dragonfly": true,
    "freebsd":   true,
    "hurd":      true,
    "illumos":   true,
    "ios":       true,
    "linux":     true,
    "netbsd":    true,
    "openbsd":   true,
    "solaris":   true,
}

这里面列出os都是所谓的“类Unix”操作系统。

如果goodOSArchFile方法返回文件名匹配成功,那么第二步就是调用Context的shouldBuild方法对Go源文件中的build constraints进行判定,这个判定过程也是调用matchTag完成的,因此规则与上面对matchTag的说明一致。如果判定match成功,那么该源文件将会被Go编译器编译到最终的Go包目标文件中去。

下面我们结合文章第一节“表象”中的那个自定义示例来判定一下为何最终会输出那个结果。

3. 示例分析

在buildconstraints/demo1/foo包目录中,一共有三个Go源文件:

$tree -F foo
foo
├── f1_android.go
├── f2_linux.go
└── f3_darwin.go

注意:当前我的系统为darwin/amd64,但我们使用了GOOS=android的环境变量。我们顺着上一节梳理出来的文件选择判定的主逻辑,对着三个文件逐一过一遍。

  • f1_android.go

首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时,文件名中的os为android,文件名匹配成功,

然后用shouldBuild判定文件中的build constraints是否匹配。该文件的约束为linux,在上面matchTag的三个例外规则里提到过,当GOOS=android时,如果build constraints是linux,是可以匹配的。

因此,f1_android.go将出现在最终编译文件列表中。

  • f2_linux.go

首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时,文件名中的os为linux,linux显然在go支持的os列表中,并且根据matchTag的例外规则,当GOOS=android时,文件名中的os为linux时是可以匹配的。

然后用shouldBuild判定文件中的build constraints是否匹配。该文件的约束为android,与GOOS相同,可以匹配。

因此,f2_linux.go将出现在最终编译文件列表中。

  • f3_darwin.go

首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时,文件名中的os为darwin,虽然darwin在go支持的os列表中,但darwin与GOOS=android并不匹配,因此在goodOSArchFile这步中,f3_darwin.go就被“淘汰”掉了!即便f3_darwin.go中的build constraints为android。

因此,f3_darwin.go不会出现在最终编译文件列表中。

如果再增加一个源文件f4_unix.go,其内容为:

//go:build android

func F4() {
}

这个f4_unix.go是否会出现在最终的包编译文件列表中呢?这个作为思考题留给大家了,也欢迎你在评论区留言,说说你的思考结果。

4. 小结

在Go语言的开发过程中,包的构建是核心环节之一,而源文件的选择则是构建过程中一个复杂且关键的细节。本文深入探讨了Go编译器在执行go build命令时,如何根据文件名中的架构(ARCH)和操作系统(OS)标识,以及构建约束(build constraints),来选择需要编译的源文件。

通过具体示例,本文展示了不同文件名和构建约束如何影响最终的编译结果,并揭示了Go编译器处理这些信息的优先级。理解这些内部机制不仅能帮助开发者优化构建过程,还能有效避免潜在的错误。希望本文的分析能够给大家带去帮助。

注:限于篇幅,本文仅针对包编译文件选择最复杂的部分进行的探索,而像ReleaseTags(比如: go1.21等)、cgo、_test.go后缀等比较明显的约束并未涉及,同时对于新版build constraints的运算符组合也未提及,感兴趣的童鞋可以参考go build constraints官方文档查阅。

本文涉及的源码可以在这里下载。

5. 参考资料


Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时,我们也会加强代码质量和最佳实践的分享,包括如何编写简洁、可读、可测试的Go代码。此外,我们还会加强星友之间的交流和互动。欢迎大家踊跃提问,分享心得,讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落,享受coding的快乐! 欢迎大家踊跃加入!

img{512x368}
img{512x368}

img{512x368}
img{512x368}

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻) – https://gopherdaily.tonybai.com

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx
  • 微博2:https://weibo.com/u/6484441286
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • Gopher Daily归档 – https://github.com/bigwhite/gopherdaily
  • Gopher Daily Feed订阅 – https://gopherdaily.tonybai.com/feed

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



Statcounter View My Stats