标签 Compiler 下的文章

Go 1.23新特性前瞻

本文永久链接 – https://tonybai.com/2024/05/30/go-1-23-foresight

2024年5月22日,Go 1.23版本功能特性正式冻结,后续将只改bug,不增加新feature。

对Go团队来说,这意味着开始了Go 1.23rc1的冲刺,对我们普通Gopher而言,这意味着是时候对Go 1.23新增的功能做一些前瞻了

在Go没有发布Go 1.23rc1之前,我们至少可以通过以下两种渠道体验Go 1.23的最新特性:

  • 通过go install安装tip版本;
  • 使用Go playground在线体验。

注:关于Go tip的安装方法以及Go playground在线体验的详细说明,这里就不赘述了,《Go语言第一课》专栏的“03|配好环境:选择一种最适合你的Go安装方法”有系统全面的讲解,欢迎订阅阅读。

按照Go Release cycle,Go 1.23将于2024年8月发布!因此目前为时尚早,下面列出的有些变化最终不一定能进入到Go 1.23的最终版本中,有小概率被revert的可能或者推迟到下一个版本(Go 1.24),所以切记一切变更点要以最终Go 1.23版本发布时为准。

1. 语言变化

Go 1.23语言变化较少,除了range over func试验特性转正,再有就是几个悬而未决的spec变更。

1.1 range over func试验特性转正(61405)

Go 1.22版本引入了range over func试验特性,通过GOEXPERIMENT=rangefunc,可以实现函数迭代器。这一特性在Go 1.23版本正式转正,下面代码可以直接使用Go 1.23编译运行:

// go1.23-foresight/lang/range-over-function-iterator/main.go

package main

import "fmt"

func Backward[E any](s []E) func(func(int, E) bool) {
    return func(yield func(int, E) bool) {
        for i := len(s) - 1; i >= 0; i-- {
            if !yield(i, s[i]) {
                return
            }
        }
        return
    }
}

func main() {
    sl := []string{"hello", "world", "golang"}
    for i, s := range Backward(sl) {
        fmt.Printf("%d : %s\n", i, s)
    }
}

使用Go 1.23运行上述示例:

$go run main.go
2 : golang
1 : world
0 : hello

range over func可以提供一种统一、高效的迭代方式, 为泛型后的自定义容器类提供统一的迭代接口,同时也可以替代部分现有API返回切片的做法, 改为通过迭代器的形式改进性能(通过编译器的优化),甚至还可以为函数式编程风格提供标准迭代机制。

rang over func机制的实现是通过编译器在源码层面的转换,其转换形式大致如下:

// 单循环变量
for x := range f1 {
    ...
}

将被转换为:

f1(func(x T) bool {
    ...
})

而另外一种常见的双循环变量形式的for range:

for expr1, expr2 = range f2 {
    ...
}

将被转换为:

f2(func(#p1 T1, #p2 T2) bool {
    expr1, expr2 = #p1, #p2
    ...
})

前提是:f1和f2分别要满足标准库中iter包中的下面函数原型形式:

// $GOROOT/src/iter/iter.go
type Seq[V any] func(yield func(V) bool) bool
type Seq2[K, V any] func(yield func(K, V) bool) bool

此外,for range循环体中如果有break,将被转换为f1/f2中的return false,而如果有continue,则会被转换为return true。当然这只是大致的形式,实际的转换远比这个要复杂很多,要考虑的情况也是十分复杂。更为具体、复杂的转换可以参考Go编译器的实现源码rewrite.go

函数迭代器虽然转正,但肯定尚未成熟,后续还会有诸多问题(比如一些corner case)需要解决,比如Russ Cox新开的issue 65236就在讨论是否允许忽略迭代变量;issue 65237将跟踪spec中与range over func相关内容的变更。

1.2 spec:几个悬而未决的issue

这个issue来自我提出的《Go 1.22引入的包级变量初始化次序问题》,Go 1.23已经修正了该问题,并保持与Go 1.22之前的版本一致,但go spec中尚未就此给出明确的说明。

一些issue已经“跳票”多次,不能确定以上issue都能最终在Go 1.23得以解决!

2. 编译器与运行时

2.1 优化了PGO(Profile Guided Optimization)带来的处理开销 (issue 58102)

Go社区发现启用PGO后,每个cmd/compile调用都会解析完整的PGO pprof配置文件,构建完整的权重图,然后确定与该包相关的内容。这类工作项有很多,并且随着Profile文件的大小和构建包的数量的扩展,构建开销也会增大。尤其是对于那些特别大的项目,PGO Profile很大,这可能会导致构建时间增加100%以上。

Go 1.23对这个问题进行了优化,PGO开销被降到了个位数百分比。

2.2 限制将来对linkname的使用(67401)

在Go语言中,//go:linkname指令可以用来链接到标准库或其他包中的未导出符号。比如我们想访问runtime包中的一个未导出函数,例如runtime.nanotime。这个函数返回当前时间的纳秒数。我们可以通过//go:linkname指令链接到这个符号。下面我用一个示例来演示一下这点:

// go1.23-foresight/compiler/golinkname/main.go
package main

import (
    "fmt"
    _ "unsafe" // 必须导入 unsafe 包以使用 //go:linkname
)

// 声明符号链接
//
//go:linkname nanotime runtime.nanotime
func nanotime() int64

func main() {
    // 调用未导出的 runtime.nanotime 函数
    fmt.Println("Current time in nanoseconds:", nanotime())
}

运行该示例:

$go run main.go
Current time in nanoseconds: 374424337532051

这种做法一般不推荐,因为它可能导致程序不稳定,并且未来版本的Go可能会改变内部实现(比如nanotime被改名或被删除),破坏你的代码。

Go团队已经意识到这一点,并发现现存开源代码中有很多代码都通过//go:linkname依赖Go项目的internal下的包或Go标准库的未导出符号。这显然不是Go团队想看到的事儿,于是Russ Cox发起issue 67401,旨在考虑限制对//go:linkname的使用。

该issue虽然在Go 1.23 milestone中,但最终是否能落在Go 1.23中还不确定,毕竟这样的调整会导致一些现存代码无法正常编译运行。

2.3 其他一些优化

  • 优化内存分配器的行为,减少了大内存(带有指针)分配时的长暂停 (issue 31222)
  • 修复Windows下time.Sleep的精度问题(issue 44343)
  • 增加了设置崩溃输出的API runtime/debug.SetCrashOutput(issue 42888)
  • 对内联器继续进行大修:重构优化 (issue 61502),这是一个长期任务,估计后续版本还会继续。

3. 工具链

3.1 新增go telemetry子命令,改进go工具链的遥测能力 (issue 67111)

Russ Cox去年初就在个人博客上发布了四篇有关Go Telemetry的文章,在2023 GopherCon大会上,Russ Cox也谈到了Go Telemetry对基于数据驱动进行Go演进决策的重要性。Russ Cox亲自创建的“all: add opt-in transparent telemetry to Go toolchain”提案也被Go项目接受。

Go工具链中的telemetry是数据驱动的重要一环,最初golang.org/x/telemetry实验项目被建立。在Go 1.23中,go工具链新增了go telemetry子命令,该子命令就是基于golang.org/x/telemetry实验项目,这也是Go团队实现某一个特性的一贯套路。

go telemetry子命令用法大致如下(以最终版本的doc为准):

go telemetry - 打印telemetry mode: on, off or local;
go telemetry on - 设置mode为on;即开启telemetry且上传遥测数据。
go telemetry local - 设置mode为local;即telemetry数据仅存储在本地,但不上传。
go telemetry off - 设置mode为off。即关闭telemetry
go clean -telemetry - 清理本地的遥测数据目录。

3.2 其他一些改变

  • go build(-json)支持以json形式输出构建结果(issue 62067),让构建结果更结构化
  • 移除了对GOROOT_FINAL的支持 (issue 62047),估计很多人不知道或完全没用过GOROOT_FINAL,我也是如此。

4. 标准库

4.1 Timer/Ticker变化

timer/ticker的stop/reset问题一直困扰Go团队,Go 1.23的两个重要fix期望能从根本上解决这个问题:

程序不再引用的Timer和Ticker将立即有资格进行垃圾回收,即使它们的Stop方法尚未被调用。Go的早期版本直到触发后才会收集未停止的Timer,并且从未收集未停止的Ticker。

  • Timer/Ticker的Stop/Reset后不再接收旧值(issue 37196)

与Timer或Ticker关联的计时器channel现在改为无缓冲的了,即容量为0 。此更改的主要效果是Go现在保证任何对Reset或Stop方法的调用,调用之前不会发送或接收任何陈旧值。 Go的早期版本使用带有缓冲区的channel,因此很难正确使用Reset和Stop。此更改的一个明显效果是计时器channel的len和cap现在返回0而不是1,这可能会影响轮询长度以确定是否在计时器channel上接收的程序。通过GODEBUG设置asynctimerchan=1可恢复异步通道行为。

4.2 新增unique包(issue 62483)

unique包的灵感来自于第三方包go4.org/intern,也是为了弥补Go语言缺乏对interning内置支持的空缺。

根据wikipedia的描述,interning是按需重复使用具有同等值对象的技术,减少创建新对象的动作。这种创建模式经常用于不同编程语言中的数和字符串,可以避免不必要的对象重复分配的开销。

Go unique包即是Go的interning机制的实现,unique包提供了一种高效的值去重和快速比较的机制,可以用于优化某些特定场景下的程序性能。

unique包提供给开发人员的API接口非常简单:Make用来创建Handle实例,Handle的方法Value用于获取值的拷贝。下面是一个使用unique包的典型示例:

// go1.23-foresight/lib/unique/main.go
package main

import (
    "fmt"
    "unique"
)

func main() {
    // 创建唯一Handle
    s1 := unique.Make("hello")
    s2 := unique.Make("world")
    s3 := unique.Make("hello")

    // s1和s3是相等的,因为它们是同一个字符串值
    fmt.Println(s1 == s3) // true
    fmt.Println(s1 == s2) // false

    // 从Handle获取原始值
    fmt.Println(s1.Value()) // hello
    fmt.Println(s2.Value()) // world
}

代码和输出结果都不难理解,这类就不赘述了。

4.3 函数迭代器相关

前面说过,函数迭代器转正了。标准库中有一些包立即就提供了一些便利的、可以与函数迭代器一起使用的函数,以slices、maps两个后加入Go标准库的泛型容器包为主。

其中slices包增加了:All、Values、Backward、Collect、AppendSeq、Sortted、SortedFunc、SortedStableFunc和Chunk。maps包增加了All、Keys、Values、Insert和Collect。

我们以slices包的All和Backward来构建一个示例,直观感受一下:

// go1.23-foresight/lib/slices/main.go

package main

import (
    "fmt"
    "slices"
)

func main() {
    sl := []string{"hello", "world", "golang"}

    for i, s := range slices.All(sl) {
        fmt.Printf("%d : %s\n", i, s)
    }

    for i, s := range slices.Backward(sl) {
        fmt.Printf("%d : %s\n", i, s)
    }
}

运行该示例:

$go run main.go
0 : hello
1 : world
2 : golang
2 : golang
1 : world
0 : hello

和以往一样,Go标准库是变化最多的一块儿,但篇幅有限,这里不便枚举,大家可以自行查看Go 1.23里程碑,选择自己关注的标准库变化,并深入研究。

5. 小结

本文主要预览了Go 1.23版本即将带来的新特性和变化。

首先在语言层面,range over func试验特性正式转正,提供统一高效的迭代方式;同时也会修复之前一些悬而未决的规范问题。

其次,在编译器和运行时方面,Go 1.23将优化PGO带来的开销,限制对linkname的使用,优化内存分配器和内联器等。工具链方面,新增telemetry子命令改进遥测能力。

标准库也有不少变化,比如Timer/Ticker的相关修复,新增unique包实现interning机制,以及为函数迭代器新增一些辅助函数。

Go 1.23的Release Notes的编写方式也做了调整,详细内容可参考我的公号文章《Go 1.23 Release Notes编写方式改进!》

总的来说,Go 1.23包含了语法、编译器、运行时、工具链和标准库等多方面的改进,其中最主要集中在编译器性能优化、PGO特性增强、新编译器功能实现以及标准库增强等方面。

不过由于Go 1.23尚未发布,文中部分变化还可能被修改或推迟到下个版本。最终还是以正式发布版为准。文末也列出了一些相关资源链接,方便读者深入了解。

截至发文时,Go 1.23 milestone已经完成59%(https://github.com/golang/go/milestone/212),还有188个open的issue待解决。究竟Go 1.23最终会做出哪些改变,让我们拭目以待!

最后,感谢Go团队以及所有Go 1.23贡献者做出的伟大工作!

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

6. 参考资料


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

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

Go 1.22中值得关注的几个变化

本文永久链接 – https://tonybai.com/2024/02/18/some-changes-in-go-1-22

美国时间2024年2月6日,正当中国人民洋溢在即将迎来龙年春节的喜庆祥和的气氛中时,Eli Bendersky代表Go团队在Go官博发文“Go 1.22 is released!”,正式向世界宣告了Go 1.22版本的发布!

注:大家可以从Go官网下载Go 1.22的第一个版本go 1.22.0,也可以在Go playground上选择Go 1.22版本在线体验Go 1.22的语法。

记忆中,这似乎是Eli Bendersky首次代表Go团队撰写Go版本发布的文章,文章短小且言简意赅,会让大家误以为Go 1.22版本没有太多的功能点变更,其实不然。读过我之前写的“Go 1.22新特性前瞻”一文的童鞋都知道Go 1.22中有很多重要且影响深远的值得我们关注的变化。在这篇文章中,我们就再来介绍一下这些变化,供大家参考。

0. 插播“旧闻”:Go再次进入Top10,并刷新有史以来的最高排名

TIOBE编程语言排行榜发布2024年2月编程语言排名的时间恰逢中国人民的传统佳节春节期间,因此它的这次排名发布“淹没”在了“龙年大吉”的喜庆气氛当中了。年后开工,大家翻看这条“旧闻”时,才发现在这次排名中,Go再一次回到Top10,位列第8名,刷新了Go打榜一来的历史最好位次。

单看这一次进入top10似乎没有什么,因为2023年4月份,Go也跻身过top10,排名第10。但如果从Go打榜以来的历史曲线来看,如下图:

我们看到了“翘尾”,我们看到了Go迈过“低谷”后的爬升!这与我在《Go语言第一课专栏》的结课语《和你一起迎接Go的黄金十年》中预判:Go即将迎来自己的黄金十年 愈来愈吻合了!

不过,我在《2023年Go语言盘点:稳中求新,稳中求变》一文中提到过TIOBE index作为世界最知名的编程语言排行榜,却存在其“不靠谱”的特性,比如这一期排名中,上古时代的编程语言Fortran从去年同期的第24位上升至第11位,仅比PHP落后一位,另一门古老的COBOL语言也从去年同期的第30位上升至第19位,仅仅比大热的Rust语言落后一位。

因此,对于TIOBE的排名,大家既要了解,也无需过于看重^_^。

言归正传,我们来说说Go 1.22版本的变化。

1. 语言变化

Go 1.22对语言语法做了两处变更,一个是Go 1.21版本中的试验特性loopvar在Go 1.22中转正落地;另一个也和for循环有关,那就是for range新增了对整型表达式的支持。两者相比较,还是第一个变化loopvar带来的影响更大一些。为什么呢?因此这是Go语言发展历史上第一次真正的填语义层面的“坑”,而且修改的是一个在Go源码中最常用的控制结构的执行语义,这很大可能会带来break change。Go101教程的作者老貘将之成为Go历史上最大的向后兼容性破坏版本

注:Go 1.21版本有一个对panic(nil)的语义修正,但我估计很少会有人写出panic(nil)这样的代码。

这次语义修改用一句话表达就是:将经典三段式for循环语句以及for range语句中的用短声明形式定义的循环变量从整个循环定义和共享一个,变为每个迭代定义一个

这里借用Go官博文章中那个例子再说明一下这个语义变化:

// go1.22-examples/lang/loopvar/main.go
package main

import (
    "fmt"
    "time"
)

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            time.Sleep(time.Second)
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

我们用Go 1.22.0版本之前的版本,比如Go 1.21.0,来运行该示例:

$go run main.go
c
c
c

我们看到:由于v是整个循环中各个迭代共享的一个变量,所以在每个迭代新创建的goroutine中输出的v都是循环结束后v的最终值c。

如果我们用go 1.22.0来运行上述示例,我们将得到:

// 输出的值的顺序与goroutine调度有关
$go run main.go
b
c
a

注:关于Go 1.22版本之前的for range的坑,我的极客时间专栏《Go语言第一课》专栏有图文并茂的原理讲解,欢迎订阅阅读。

那么,loopvar这一语义填“坑”究竟会对你的代码造成怎样的影响呢?在Russ Cox关于loopvar语义变更的设计文档中提到了:只有go.mod中的go version在go 1.22.0及以后的时候才会生效,这是一个渐进式过渡的过程,因此目前无论是开源项目还是商业项目,只要go.mod中的go version还没有更新为大于等于go 1.22.0,那么for循环依然会保留短声明定义的变量的原语义,这样这些项目都不会受到影响。

不过,如果是直接在脚本中通过go run xxx.go形式运行某个go源码的,且当前工作目录以及父目录下没有go.mod文件的,go 1.22.0会采用新的loopvar语义,这点大家要注意了。

此外,当你将go.mod中的go version升级到go 1.22.0或更高版本时,也要注意语义变更可能带来的问题。在升级go version之前,可以用Go 1.22版本之前的go vet对项目源码进行一次静态分析,对于go vet提示:“loop variable v captured by func literal”的地方务必注意逐个确认。

注:Go 1.22版本中的go vet已经移除了在go version >= 1.22.0时,对“loop variable v captured by func literal”情况进行警告的功能。

关于Go 1.22中for range支持后面接整型表达式的“语法糖”新特性以及函数迭代器的实验特性,这里就不细说了,大家可以看看“Go 1.22新特性前瞻”一文中的说明。

2. 编译器、运行时与工具链

在编译器、运行时和工具链这些方面,Go 1.22的正式版本与“Go 1.22新特性前瞻”一文中使用的Go 1.22rc1版本几乎没有差异,这里挑主要内容介绍一下,其他一些内容可以参考前瞻一文。

Go 1.22版本继续在编译上优化PGO(profile-guided optimization), 基于PGO的构建可以比以前版本实现更高比例的调用去虚拟化(devirtualize)。在Go 1.22中,官⽅给出的PGO带来的性能提升数字是2%~14%,这应该是基于Google内部一些典型的Go程序测算出来的。

注:如果你对PGO优化还不是很了解,可以看看“深入理解Profile Guided Optimization(PGO)”这篇文章。

Go 1.22版本编译器现在可以更多运⽤devirtualize和inline。在Go编译器中,devirtualize是一种编译优化技术,旨在消除“虚函数”调用的开销。“虚函数”是指在面向对象编程中,通过基类指针或引用调用的函数。在Go中所谓虚函数调用指的就是通过接口类型变量进行的方法调用。由于是动态调用,基于接口的方法调用需要在运行时进行查找和分派,这可能导致一定的性能损失。

而Go编译器在进行devirtualize优化时,会尝试根据程序的上下文信息和类型信息,确定方法调用的具体对象实例。如果编译器能够确定调用的具体实例,则会将通过接口的方法调用替换为直接调用具体对象实例的方法,从而消除运行时的开销,使得通过接口类型变量进行方法调用的性能得到优化提升。

Go 1.22版本中的运行时可以使基于类型的垃圾收集的元数据更接近每个堆对象,从而将Go程序的CPU性能(延迟或吞吐量)提高了1-3%。这一变化还支持通过重复数据删除冗余元数据,进而将大多数Go程序的内存开销减少了大约1%。

在工具链方面,有三个主要改变这里提一下:

  • go work支持vendor

在Go 1.22版本中,通过go work vendor可以将workspace中的依赖放到vendor⽬录下,同时在构建时,如果workspace下有vendor⽬录,那么默认的构建是go build -mod=vendor,即基于vendor的构建。

  • go mod init不再care其他vendor工具的配置文件

go mod init不再尝试将其他vendor工具(例如Gopkg.lock )的配置文件导入到go module依赖文件(go.mod)中了,也就是说从Go 1.22版本开始,go module出现之前的那些gopath时代的依赖管理工具正式退出并成为历史了。

  • 改进go test -cover的输出

对于没有自己的测试文件的包,go test -cover在go 1.22版本之前会输出:

? mymod/mypack [no test files]

但在Go 1.22版本之后,会报告覆盖率为0.0%:

mymod/mypack coverage: 0.0% of statements

3. 标准库

这里列举一下标准库值得关注的重大变化,大家可以与前瞻一文相互参考着阅读。

3.1 math/rand/v2:标准库的第一个v2版本包

Go 1.22中新增了math/rand/v2包,这里之所以将它列为Go 1.22版本标准库的⼀次重要变化,是因为这是标准库第一次为某个包建⽴v2版本,按照Russ Cox的说法,这次math/rand/v2包的创建,算是为标准库中的其他可能的v2包“探探路”,找找落地路径。关于math/rand/v2包相对于原math/rand包的变化有很多,具体可以参考issue 61716中的设计与讨论。

3.2 增强http.ServeMux表达能力

在Go 1.22版本中,http.ServeMux的表达能力得到了大幅提升,从原先只支持静态路由,到新版本中支持如下一些特性:

  • “/index.html”路由将匹配任何主机和方法的路径”/index.html”;
  • “GET /static/”将匹配路径以”/static/”开头的GET请求;
  • “example.com/”可以与任何指向主机为”example.com”的请求匹配;
  • “example.com/{$}”会匹配主机为”example.com”、路径为”/”的请求,即”example.com/”;
  • “/b/{bucket}/o/{objectname…}”匹配第一段为”b”、第三段为”o”的路径。名称”bucket”表示第二段,”objectname”表示路径的其余部分。

并且新版ServeMux在路由匹配性能方面也不输众多开源http路由框架太多,后续建立Go web或api类新项目时,可以考虑首选新版ServeMux来进行路由匹配了,减少对外部的一个依赖。

关于新版http.ServeMux的具体使用方法,其作者Jonathan Amsterdam(也是log/slog的作者)在官博发表了一篇名为“Routing Enhancements for Go 1.22”的文章,大家可以详细参考。

关于标准库的其他一些变化,大家可以参考前瞻一文以及更详细的Go 1.22的发布说明文档

4. 小结

综上,Go 1.22版本对语言、编译器、工具链、运行时和标准库都有一定程度的改进和创新,遗留代码通过Go 1.22版本的重新编译便可以得到一定程度的性能上的自然提升,这也体现了Go语言在稳中求新、稳中求变的特点。

不过这里还要提醒各位Go开发者,在升级Go 1.22版本时务必注意潜在的向后兼容性问题,尤其是loopvar语义带来的变化影响。

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


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

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

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! 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

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats