视频笔记:Go 的构建模式 - David Crawshaw
大桥下的蜗牛 · · 1727 次点击 · · 开始浏览视频信息 #
Go Build Modes
by David Crawshaw, Google
at GopherCon 2017
https://www.youtube.com/watch?v=x-LhC-J2Vbk
什么是 Build Mode? #
build mode 用于指导编译器如何创建可执行二进制文件。越多的执行方式,就意味着可以让 Go 程序运行于更多的位置。
Go 的八种 Build Mode #
exe(静态编译)exe(动态链接libc)exe(动态链接libc和非 Go 代码)pie地址无关可执行文件(安全特性)c-archiveC 的静态链接库c-sharedC 的动态链接库sharedGo 的动态链接库pluginGo 的插件
exe (静态编译) #
这个是大家最喜欢的,所有的代码都构建到一个可执行文件了。
1
CGO_ENABLED=0 go build hello.go
这是大家使用 Go 最喜欢的构建方式。所有的依赖都构建到了一个二进制文件了,没有任何外部依赖,可执行文件直接调用 syscall 和内核通讯。
这里使用 CGO_ENABLED=0 来约束不使用任何 CGO 的部分,这样不会依赖 libc 这类库。
exe (用 libc) #
这样的可执行文件大部分都是静态编译,只不过使用了 libc 动态链接库,因此像一些 net 包的操作,比如 DNS 查询、os/user 的用户名查询等等,这些会使用系统提供的 libc 动态链接库。
其好处是,可以利用系统特定的实现,保证行为和系统一致。
exe (动态链接 libc 和非 Go 代码) #
当程序编译的时候,所有 Go 代码自然都被编译为 object 文件,而所有非 Go 的代码,也可以被被其编译器(如 C, Fortran 等)编译为 object 文件,而这些非 Go 代码可以被 cgo 调用。
当程序被连接(link)的时候,这些非 Go 代码可以选择被编译进最终的二进制文件中,也可以选择动态链接,在运行时加载。
pie - Position Independent Executables #
这是构建运行地址无关的二进制可执行文件的形式,这是一种安全特性,可以在支持 PIE 的操作系统中,让可执行文件在加载时,每次的地址都是不同的。避免已知地址的跳跃式的攻击。
这种方式和 exe 基本一样,将来可能会成为默认。
c-archive C 的静态链接库 #
从这里开始,和前面构建可执行文件不同了。这里构建的是供 C 程序调用的库。更准确一些的说,这里是把 Go 程序构建为 archive (.a) 文件,这样 C 类的程序可以静态链接 .a 文件,并调用其中代码。
hello.go
1234567891011
package mainimport "fmt"import "C"func main() {}//export Hellofunc Hello() {fmt.Println("Hello, world.")}
注意这里的
//export Hello,这是约定,所有需要导出给C调用的函数,必须通过注释添加这个构建信息,否则不会构建生成 C 所需的头文件。
然后我们构建这个 hello.go 文件:
1
go build -buildmode=c-archive hello.go
构建后,会生成两个文件,一个是静态库文件 hello.a,另一个则是 C 的头文件 hello.h。
12
hello.a: current ar archive random libraryhello.h: c program text, ASCII text
在所生成的 hello.h 的头文件中,我们可以看到 Go 的 Hello() 函数的定义:
12345678910
#ifdef __cplusplusextern "C" {#endifextern void Hello();#ifdef __cplusplus}#endif
然后我们可以在 hello.c 中引用头文件,并使用 Go 编译的静态库:
123456
#include "hello.h"int main(void) {Hello();return 0;}
然后,构建 C 程序:
1
cc hello.a hello.c -o hello
最后执行:
12
$ ./helloHello, world.
c-shared C 的动态链接库 #
和前一个例子不同的地方是,这将用 Go 代码创建一个动态链接库(Unix: .so/Windows .dll),然后用 C 语言程序动态加载运行。
Go 和 C 语言的代码和上面是一样的,但是构建过程不同:
1
go build -buildmode=c-shared -o hello.so hello.go
这里我们使用了 -buildmode=c-shared,以构建 C 所支持的动态链接库。
注:需要注意的是,这里明确指定了
-o hello.so,这里我和演讲者不同,如果不指定输出文件名,那么默认会使用hello作为文件名,导致后续的操作找不到hello.so文件。
这次也生成了两个文件,一个是 hello.so,一个是 hello.h:
12
hello.h: c program text, ASCII texthello.so: Mach-O 64-bit dynamically linked shared library x86_64
然后,编译对应的 C 程序:
1
cc hello.c hello.so -o hello
如果对比 c-archive 例子和 c-shared 例子中的 hello 二进制可执行文件的大小,就会发现 c-shared 的例子的 hello 要小很多:
12345
# c-archive-rwxr-xr-x 1 taowang staff 1.5M 3 Oct 17:51 hello# c-shared-rwxr-xr-x 1 taowang staff 8.2K 3 Oct 19:17 hello
这是因为前者,将 Go 的代码静态编译进了 C 的程序中;而后者,则是动态链接,C 的可执行文件内不包含我们写的 Go 的代码,所有这部分函数都在动态链接库 hello.so 中。
1
-rw-r--r-- 1 taowang staff 2.2M 3 Oct 19:17 hello.so
因此,执行的时候,我们除了需要 hello 这个二进制可执行文件外,我们还需要 hello.so 这个动态链接库。如果默认的 LD_LIBRARY_PATH 包含了当前目录,并且 hello.so 就在当前目录,那么可以直接:
12
$ ./helloHello, world.
否则,如果提示找不到 hello.so,如:
1
dyld: Library not loaded: hello.so
那可以手动指定 LD_LIBRARY_PATH 变量,告诉操作系统到哪里去寻找动态链接库:
1234567
# On Linux$ LD_LIBRARY_PATH=. ./helloHello, world.# On macOS$ DYLD_LIBRARY_PATH=. ./helloHello, world.
为什么会需要动态链接? #
从开始使用 Go 我们就反反复复的听到人说 Go 的静态链接如何方便,既然如此,那么我们为什么需要动态链接?
因为动态链接可以在运行时需要的时候,由程序决定加载,也可以在不需要的时候卸载,这样可以节约内存资源。
123456789101112131415
#include <dlfcn.h>#include <stdio.h>int main(void) {void* lib = dlopen("hello", 0);void (*fn)() = dlsym(lib, "Hello");if (!fn) {fprintf(stderr, "no fn: %s\n", dlerror());return 1;}// Calls Hello();fn();return 0;}
这里我们使用 dlopen() 来加载库,然后用 dlsym() 来加载符号(函数)到一个函数指针,然后我们调用该函数指针 fn()。
shared Go 的动态链接库 #
shared 模式和 c-shared 有些相似,都是构建一个动态链接库,以便在运行时加载。所不同的是 shared 并非构建 C 语言的动态链接库,而是专门为 Go 可执行文件构建动态链接库。
macOS 下目前不支持
shared模式。
这次还是 hello.go,不过稍有不同。
1234567
package mainimport "fmt"func main() {fmt.Println("Hello, World!")}
这里就是独立的一个文件,一个 main(),执行后打印 Hello, World。我们可以像以前一样用 exe 模式构建,然后执行。不过这次我们用一种不同的方式构建。
12
go install -buildmode=shared stdgo build -linkshared hello.go
这里我们首先把 Go 标准库 std 构建并安装到 $GOPATH/pkg 下,然后使用 -linkshared 来构建 hello.go。
执行结果和前面一样,但是如果仔细观察生成的文件,就会发现和前面很不同。
12
$ ls -l hello-rwxr-xr-x 1 root root 16032 Oct 3 13:27 hello
可以看到这个 Hello World 程序只有十几KB大小。对于 C 程序员来说,这没啥惊讶的,因为就应该这么大啊。但是对于 Go 程序员来说,这就是很奇怪了,因为一般不都得 7~8MB 么?
其原因就是使用了动态链接库,所有标准库部分,都用动态链接的办法来调用,构建的二进制可执行文件中只包含了程序部分。C 程序构建的 Hello World 之所以小,也是因为动态链接的原因。
如果我们查阅程序所调用的库就可以看到具体情况:
1234567
$ ldd hellolinux-vdso.so.1 (0x00007ffed3d4e000)libstd.so => /usr/local/go/pkg/linux_amd64_dynlink/libstd.so (0x00007f608c409000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f608c06a000)libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f608be66000)libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f608bc49000)/lib64/ld-linux-x86-64.so.2 (0x00007f608e866000)
如果我们进一步去查看 libstd.so,就会看到一个巨大的动态链接库,这就是 Go 的标准库:
1
-rw-r--r-- 1 root root 37M Oct 3 13:27 /usr/local/go/pkg/linux_amd64_dynlink/libstd.so
当然,要使用这个模式需要很多准备工作,所有的动态链接库都需要在指定的位置,版本都必须兼容等等,所以我们一般不常用这个模式。
plugin Go 的插件 #
插件形式和 c-shared、shared 相似,都是构建一个动态链接库,和 shared 一样,这是构建一个 Go 专用的动态链接库,而和 shared 不同的是,动态链接库并非在程序启动时加载,而是由程序内决定何时加载和释放。
这是个新的东西,所以意味着可能不能用?......,当然如果用的对的话,应该还可以用。
我们创建一个 plugin,myplugin.go:
1234567
package mainimport "fmt"func Hello() {fmt.Println("Hello, World!")}
可以看到,这和最初那个静态链接库的性质相似。不过不同的是,这里既没有 import "C",也没有 //export Hello,而且也没有 func main()。因为这里不需要,我们是 Go 调用 Go 的代码,因此很多东西都省了。
调用代码这么写:
123456789101112131415161718
package mainimport "plugin"func main() {// 加载 myplugin 库p, err := plugin.Open("myplugin.so")if err != nil {log.Fatal(err)}// 取得 Hello 函数fn, err := p.Lookup("Hello")if err != nil {log.Fatal(err)}// 调用函数fn.(func())()}
可以看到,这个逻辑上,和 hello-dyn.c 很相似。plugin.Open() 有点儿像 dlopen();而 p.Lookup() 有点儿像 dlsym()。实际上也是如此,底层实现的时候就是调用的这两个函数。
注意这里的 fn.(func())(),p.Lookup() 返回的是一个 interface{},因此这里需要转型为具体函数类型。
用下面的命令构建:
12
go build -buildmode=plugin myplugin.gogo build runplugin.go
前者会生成一个 myplugin.so,后者会生成调用者 runplugin。
12
-rw-r--r-- 1 root root 3.8M Oct 3 13:58 myplugin.so-rwxr-xr-x 1 root root 3.5M Oct 3 13:58 runplugin
优缺点 #
exe(静态编译)- Pros:
- 全部集成,不需要任何依赖
- 非常适合超小型的容器环境
- 很容易跨不同 Linux 发行版
- Pros:
exe(动态链接libc)- Pros:
- 可以利用系统功能,比如 DNS 查询。
- 可以通过
libc直接使用系统配置。
- Cons:
- 依赖用户空间的执行环境
- Pros:
exe(动态链接libc和非 Go 代码)- Pros:
- 可以直接在 Go 程序中使用非 Go 的代码
- 方便和老的系统集成
- Cons:
- 构建变得更加复杂
- C 不是 Go
- 更容易出问题。
- 所有 Go 可能出问题地方
- 所有 C 可能出问题的地方
- 所有 Go <-> C 之间通讯可能出问题的地方
- Pros:
pie地址无关可执行文件(安全特性)- Pros:
- 和
exe一样 - 让系统更难攻击
- 和
- Cons:
- 二进制会更大一些(bug, will be fixed)
- 大约会有
~1%的性能损失
- Pros:
c-archiveC 的静态链接库- Pros:
- 可以让 Go 集成到现有的 C 程序中
- 事实上,这就是 Go 在 iOS 上的工作方式
- 非常适用于已存在的非 Go 环境的构建
- Cons:
- 跨语言调用会比较麻烦
- Pros:
c-sharedC 的动态链接库- Pros:
- 比较方便 Go 集成进现有的 C 程序中
- 可以在运行时加载
- 这是目前 Go 在 Android 下的工作方式(Java 的
System.load())
- Cons:
- 跨语言调用会比较麻烦
- 想想 Android 的环境,可能出问题的面积更大了:
- Go 可能出问题的地方
- C 可能出问题的地方
- Java 可能出问题的地方
- 所有它们之间通讯可能出问题的地方......?
- Pros:
sharedGo 的动态链接库- Pros:
- 多个可执行文件可以共享动态链接库,可以降低系统总的体积。
- 一般操作系统厂商会比较青睐于这种方式,可以让整个系统的体积降低。
- 事实上,这就是 Canonical(Ubuntu) 力推实现的方式
- 可以降低系统体积,不过现在存储空间一般不是问题
- 可以降低内存,可以在内存中共享动态链接库代码(如果动态链接库的 loader 足够聪明的话)
- Cons:
- 依赖管理、以及发布是非常难得
- 不过一般操作系统厂商已经有成熟的发布系统了
- 依赖管理、以及发布是非常难得
- Pros:
pluginGo 的插件- Pros:
- 在运行时 Go 程序加载其它 Go 程序
- 对于复杂应用来说,允许不同部分在不同时间构建
- Cons:
- 构建比较复杂,部署也会很复杂
- 如果问演讲者是否该用
plugin模式,答案一般是 No。
- Pros:
未来 #
还有很多地方需要改进。
c-shared:目前不支持 Windows(可能),macOS(部分支持)shared:目前不支持 macOSplugin:目前不支持 macOS、Windowsplugin:或许可以将runtime从插件中移除以获得更小的可执行文件
有疑问加站长微信联系(非本文作者)
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传
收入到我管理的专栏 新建专栏
视频信息 #
Go Build Modes
by David Crawshaw, Google
at GopherCon 2017
https://www.youtube.com/watch?v=x-LhC-J2Vbk
什么是 Build Mode? #
build mode 用于指导编译器如何创建可执行二进制文件。越多的执行方式,就意味着可以让 Go 程序运行于更多的位置。
Go 的八种 Build Mode #
exe(静态编译)exe(动态链接libc)exe(动态链接libc和非 Go 代码)pie地址无关可执行文件(安全特性)c-archiveC 的静态链接库c-sharedC 的动态链接库sharedGo 的动态链接库pluginGo 的插件
exe (静态编译) #
这个是大家最喜欢的,所有的代码都构建到一个可执行文件了。
1
CGO_ENABLED=0 go build hello.go
这是大家使用 Go 最喜欢的构建方式。所有的依赖都构建到了一个二进制文件了,没有任何外部依赖,可执行文件直接调用 syscall 和内核通讯。
这里使用 CGO_ENABLED=0 来约束不使用任何 CGO 的部分,这样不会依赖 libc 这类库。
exe (用 libc) #
这样的可执行文件大部分都是静态编译,只不过使用了 libc 动态链接库,因此像一些 net 包的操作,比如 DNS 查询、os/user 的用户名查询等等,这些会使用系统提供的 libc 动态链接库。
其好处是,可以利用系统特定的实现,保证行为和系统一致。
exe (动态链接 libc 和非 Go 代码) #
当程序编译的时候,所有 Go 代码自然都被编译为 object 文件,而所有非 Go 的代码,也可以被被其编译器(如 C, Fortran 等)编译为 object 文件,而这些非 Go 代码可以被 cgo 调用。
当程序被连接(link)的时候,这些非 Go 代码可以选择被编译进最终的二进制文件中,也可以选择动态链接,在运行时加载。
pie - Position Independent Executables #
这是构建运行地址无关的二进制可执行文件的形式,这是一种安全特性,可以在支持 PIE 的操作系统中,让可执行文件在加载时,每次的地址都是不同的。避免已知地址的跳跃式的攻击。
这种方式和 exe 基本一样,将来可能会成为默认。
c-archive C 的静态链接库 #
从这里开始,和前面构建可执行文件不同了。这里构建的是供 C 程序调用的库。更准确一些的说,这里是把 Go 程序构建为 archive (.a) 文件,这样 C 类的程序可以静态链接 .a 文件,并调用其中代码。
hello.go
1234567891011
package mainimport "fmt"import "C"func main() {}//export Hellofunc Hello() {fmt.Println("Hello, world.")}
注意这里的
//export Hello,这是约定,所有需要导出给C调用的函数,必须通过注释添加这个构建信息,否则不会构建生成 C 所需的头文件。
然后我们构建这个 hello.go 文件:
1
go build -buildmode=c-archive hello.go
构建后,会生成两个文件,一个是静态库文件 hello.a,另一个则是 C 的头文件 hello.h。
12
hello.a: current ar archive random libraryhello.h: c program text, ASCII text
在所生成的 hello.h 的头文件中,我们可以看到 Go 的 Hello() 函数的定义:
12345678910
#ifdef __cplusplusextern "C" {#endifextern void Hello();#ifdef __cplusplus}#endif
然后我们可以在 hello.c 中引用头文件,并使用 Go 编译的静态库:
123456
#include "hello.h"int main(void) {Hello();return 0;}
然后,构建 C 程序:
1
cc hello.a hello.c -o hello
最后执行:
12
$ ./helloHello, world.
c-shared C 的动态链接库 #
和前一个例子不同的地方是,这将用 Go 代码创建一个动态链接库(Unix: .so/Windows .dll),然后用 C 语言程序动态加载运行。
Go 和 C 语言的代码和上面是一样的,但是构建过程不同:
1
go build -buildmode=c-shared -o hello.so hello.go
这里我们使用了 -buildmode=c-shared,以构建 C 所支持的动态链接库。
注:需要注意的是,这里明确指定了
-o hello.so,这里我和演讲者不同,如果不指定输出文件名,那么默认会使用hello作为文件名,导致后续的操作找不到hello.so文件。
这次也生成了两个文件,一个是 hello.so,一个是 hello.h:
12
hello.h: c program text, ASCII texthello.so: Mach-O 64-bit dynamically linked shared library x86_64
然后,编译对应的 C 程序:
1
cc hello.c hello.so -o hello
如果对比 c-archive 例子和 c-shared 例子中的 hello 二进制可执行文件的大小,就会发现 c-shared 的例子的 hello 要小很多:
12345
# c-archive-rwxr-xr-x 1 taowang staff 1.5M 3 Oct 17:51 hello# c-shared-rwxr-xr-x 1 taowang staff 8.2K 3 Oct 19:17 hello
这是因为前者,将 Go 的代码静态编译进了 C 的程序中;而后者,则是动态链接,C 的可执行文件内不包含我们写的 Go 的代码,所有这部分函数都在动态链接库 hello.so 中。
1
-rw-r--r-- 1 taowang staff 2.2M 3 Oct 19:17 hello.so
因此,执行的时候,我们除了需要 hello 这个二进制可执行文件外,我们还需要 hello.so 这个动态链接库。如果默认的 LD_LIBRARY_PATH 包含了当前目录,并且 hello.so 就在当前目录,那么可以直接:
12
$ ./helloHello, world.
否则,如果提示找不到 hello.so,如:
1
dyld: Library not loaded: hello.so
那可以手动指定 LD_LIBRARY_PATH 变量,告诉操作系统到哪里去寻找动态链接库:
1234567
# On Linux$ LD_LIBRARY_PATH=. ./helloHello, world.# On macOS$ DYLD_LIBRARY_PATH=. ./helloHello, world.
为什么会需要动态链接? #
从开始使用 Go 我们就反反复复的听到人说 Go 的静态链接如何方便,既然如此,那么我们为什么需要动态链接?
因为动态链接可以在运行时需要的时候,由程序决定加载,也可以在不需要的时候卸载,这样可以节约内存资源。
123456789101112131415
#include <dlfcn.h>#include <stdio.h>int main(void) {void* lib = dlopen("hello", 0);void (*fn)() = dlsym(lib, "Hello");if (!fn) {fprintf(stderr, "no fn: %s\n", dlerror());return 1;}// Calls Hello();fn();return 0;}
这里我们使用 dlopen() 来加载库,然后用 dlsym() 来加载符号(函数)到一个函数指针,然后我们调用该函数指针 fn()。
shared Go 的动态链接库 #
shared 模式和 c-shared 有些相似,都是构建一个动态链接库,以便在运行时加载。所不同的是 shared 并非构建 C 语言的动态链接库,而是专门为 Go 可执行文件构建动态链接库。
macOS 下目前不支持
shared模式。
这次还是 hello.go,不过稍有不同。
1234567
package mainimport "fmt"func main() {fmt.Println("Hello, World!")}
这里就是独立的一个文件,一个 main(),执行后打印 Hello, World。我们可以像以前一样用 exe 模式构建,然后执行。不过这次我们用一种不同的方式构建。
12
go install -buildmode=shared stdgo build -linkshared hello.go
这里我们首先把 Go 标准库 std 构建并安装到 $GOPATH/pkg 下,然后使用 -linkshared 来构建 hello.go。
执行结果和前面一样,但是如果仔细观察生成的文件,就会发现和前面很不同。
12
$ ls -l hello-rwxr-xr-x 1 root root 16032 Oct 3 13:27 hello
可以看到这个 Hello World 程序只有十几KB大小。对于 C 程序员来说,这没啥惊讶的,因为就应该这么大啊。但是对于 Go 程序员来说,这就是很奇怪了,因为一般不都得 7~8MB 么?
其原因就是使用了动态链接库,所有标准库部分,都用动态链接的办法来调用,构建的二进制可执行文件中只包含了程序部分。C 程序构建的 Hello World 之所以小,也是因为动态链接的原因。
如果我们查阅程序所调用的库就可以看到具体情况:
1234567
$ ldd hellolinux-vdso.so.1 (0x00007ffed3d4e000)libstd.so => /usr/local/go/pkg/linux_amd64_dynlink/libstd.so (0x00007f608c409000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f608c06a000)libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f608be66000)libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f608bc49000)/lib64/ld-linux-x86-64.so.2 (0x00007f608e866000)
如果我们进一步去查看 libstd.so,就会看到一个巨大的动态链接库,这就是 Go 的标准库:
1
-rw-r--r-- 1 root root 37M Oct 3 13:27 /usr/local/go/pkg/linux_amd64_dynlink/libstd.so
当然,要使用这个模式需要很多准备工作,所有的动态链接库都需要在指定的位置,版本都必须兼容等等,所以我们一般不常用这个模式。
plugin Go 的插件 #
插件形式和 c-shared、shared 相似,都是构建一个动态链接库,和 shared 一样,这是构建一个 Go 专用的动态链接库,而和 shared 不同的是,动态链接库并非在程序启动时加载,而是由程序内决定何时加载和释放。
这是个新的东西,所以意味着可能不能用?......,当然如果用的对的话,应该还可以用。
我们创建一个 plugin,myplugin.go:
1234567
package mainimport "fmt"func Hello() {fmt.Println("Hello, World!")}
可以看到,这和最初那个静态链接库的性质相似。不过不同的是,这里既没有 import "C",也没有 //export Hello,而且也没有 func main()。因为这里不需要,我们是 Go 调用 Go 的代码,因此很多东西都省了。
调用代码这么写:
123456789101112131415161718
package mainimport "plugin"func main() {// 加载 myplugin 库p, err := plugin.Open("myplugin.so")if err != nil {log.Fatal(err)}// 取得 Hello 函数fn, err := p.Lookup("Hello")if err != nil {log.Fatal(err)}// 调用函数fn.(func())()}
可以看到,这个逻辑上,和 hello-dyn.c 很相似。plugin.Open() 有点儿像 dlopen();而 p.Lookup() 有点儿像 dlsym()。实际上也是如此,底层实现的时候就是调用的这两个函数。
注意这里的 fn.(func())(),p.Lookup() 返回的是一个 interface{},因此这里需要转型为具体函数类型。
用下面的命令构建:
12
go build -buildmode=plugin myplugin.gogo build runplugin.go
前者会生成一个 myplugin.so,后者会生成调用者 runplugin。
12
-rw-r--r-- 1 root root 3.8M Oct 3 13:58 myplugin.so-rwxr-xr-x 1 root root 3.5M Oct 3 13:58 runplugin
优缺点 #
exe(静态编译)- Pros:
- 全部集成,不需要任何依赖
- 非常适合超小型的容器环境
- 很容易跨不同 Linux 发行版
- Pros:
exe(动态链接libc)- Pros:
- 可以利用系统功能,比如 DNS 查询。
- 可以通过
libc直接使用系统配置。
- Cons:
- 依赖用户空间的执行环境
- Pros:
exe(动态链接libc和非 Go 代码)- Pros:
- 可以直接在 Go 程序中使用非 Go 的代码
- 方便和老的系统集成
- Cons:
- 构建变得更加复杂
- C 不是 Go
- 更容易出问题。
- 所有 Go 可能出问题地方
- 所有 C 可能出问题的地方
- 所有 Go <-> C 之间通讯可能出问题的地方
- Pros:
pie地址无关可执行文件(安全特性)- Pros:
- 和
exe一样 - 让系统更难攻击
- 和
- Cons:
- 二进制会更大一些(bug, will be fixed)
- 大约会有
~1%的性能损失
- Pros:
c-archiveC 的静态链接库- Pros:
- 可以让 Go 集成到现有的 C 程序中
- 事实上,这就是 Go 在 iOS 上的工作方式
- 非常适用于已存在的非 Go 环境的构建
- Cons:
- 跨语言调用会比较麻烦
- Pros:
c-sharedC 的动态链接库- Pros:
- 比较方便 Go 集成进现有的 C 程序中
- 可以在运行时加载
- 这是目前 Go 在 Android 下的工作方式(Java 的
System.load())
- Cons:
- 跨语言调用会比较麻烦
- 想想 Android 的环境,可能出问题的面积更大了:
- Go 可能出问题的地方
- C 可能出问题的地方
- Java 可能出问题的地方
- 所有它们之间通讯可能出问题的地方......?
- Pros:
sharedGo 的动态链接库- Pros:
- 多个可执行文件可以共享动态链接库,可以降低系统总的体积。
- 一般操作系统厂商会比较青睐于这种方式,可以让整个系统的体积降低。
- 事实上,这就是 Canonical(Ubuntu) 力推实现的方式
- 可以降低系统体积,不过现在存储空间一般不是问题
- 可以降低内存,可以在内存中共享动态链接库代码(如果动态链接库的 loader 足够聪明的话)
- Cons:
- 依赖管理、以及发布是非常难得
- 不过一般操作系统厂商已经有成熟的发布系统了
- 依赖管理、以及发布是非常难得
- Pros:
pluginGo 的插件- Pros:
- 在运行时 Go 程序加载其它 Go 程序
- 对于复杂应用来说,允许不同部分在不同时间构建
- Cons:
- 构建比较复杂,部署也会很复杂
- 如果问演讲者是否该用
plugin模式,答案一般是 No。
- Pros:
未来 #
还有很多地方需要改进。
c-shared:目前不支持 Windows(可能),macOS(部分支持)shared:目前不支持 macOSplugin:目前不支持 macOS、Windowsplugin:或许可以将runtime从插件中移除以获得更小的可执行文件