关于go:在-Go-里用-CGO这-7-个问题你要关注

3次阅读

共计 3143 个字符,预计需要花费 8 分钟才能阅读完成。

大家好,我是煎鱼。

明天给大家分享的是 Go 谚语中的 Cgo is not Go,原文章同名,略有批改,作者是 @Dave Cheney。以下的“我”均指代原作者。

借用 JWZ 的一句话:有些人在面对一个问题时,认为 “ 我晓得,我会应用 cgo(来解决)”。

在应用 cgo 后,他们就会遇到两个新问题。

Cgo 是什么

Cgo 是一项了不起的技术,它容许 Go 程序与 C 语言库互相操作,这是一个十分有用的性能。

没有它,Go 就不会有明天的位置。cgo 是在 Android 和 iOS 上运行 Go 程序的要害。

被适度应用

我集体认为 cgo 在 Go 我的项目中被适度应用了,当面临在 Go 中从新实现一大段 C 语言代码时,程序员会抉择应用 cgo 来包装库,认为这是个更容易解决的问题。但我认为这是一种谬误的抉择行为。

显然,在某些状况下,cgo 是不可避免的,最显著的是你必须与图形驱动或窗口零碎进行互操作,而后者只能以二进制 blob 的模式提供。在这些场景下,cgo 的应用证实了它的衡量是正当的,比许多人筹备抵赖的要少得多。

以下是一份不残缺的衡量清单,当你把 Go 我的项目建设在 cgo 库上时,你可能没有意识到这些衡量。

你须要对此进行思考。

构建工夫变长

当你在 Go 包中导入 “C” 时,go build 须要做更多的工作来构建你的代码。

构建你的包不再是简略地将范畴内的所有 .go 文件的列表传递给 go 工具编译的一次调用,而是蕴含以下工作项:

  • 须要调用 cgo 工具来生成 C 到 Go 和 Go 到 C 的相干代码。
  • 零碎中的 C 编译器会为软件包中的每个 C 文件进行调用解决。
  • 各个编译单元被合并到一个 .o 文件中。
  • 生成的 .o 文件会通过零碎的链接器,对其援用的共享对象进行修改。

所有这些工作在你每次编译或测试你的软件包时都会产生,如果你在该软件包中踊跃工作的话,这种状况是常常产生的。

Go 工具会在可能的状况下将这些工作并行化(包含对所有的 C 代码进行全面重建),软件包的编译工夫将会减少,并会随之增大而增大。

你还须要在各大平台上调试你的 C 语言代码,以防止因为兼容性导致的编译失败。

简单的构建

Go 的指标之一是产生一种语言,它的构建过程是自我形容的;你的程序的源代码蕴含了足够的信息,能够让一个工具来构建这个我的项目。这并不是说应用 Makefile 来主动构建工作流程是不好的,然而在 cgo 被引入我的项目之前,除了 go 工具之外,你可能不须要任何货色来构建和测试。

在引入了 cgo 之后,你须要设置所有的环境变量,跟踪可能装置在奇怪中央的共享对象和头文件。

另外须要留神,Go 反对许多的平台,而 cgo 并不是。所以你必须花一些工夫来为你的 Windows 用户想出一个解决方案。

当初你的用户必须装置 C 编译器,而不仅仅是 Go 编译器。他们还必须装置你的我的项目所依赖的 C 语言库,你也要承当这个技术支持的老本。

穿插汇编被抛在窗外

Go 对穿插编译的反对是同类中最好的。从 Go 1.5 开始,你能够通过 Go 我的项目网站上的官网安装程序反对从任何平台穿插编译到任何其余平台。

在默认状况下,穿插编译时 cgo 被禁用。通常状况下,如果你的我的项目是纯正的 Go,这不是一个问题。

当你混入对 C 库的依赖时,你要么放弃穿插编译你的因那个也,要么你必须投入工夫为所有指标寻找和保护穿插编译的 C 工具链,能力实现穿插编译。

Go 反对的平台数量在一直减少。Go 1.5 减少了对 64 位 ARM 和 PowerPC 的反对。Go 1.6 减少了对 64 位 MIPS 的反对,而 IBM 的 s390 架构被吹捧为 Go 1.7。RISC-V 正在开发中。

如果你的产品依赖于 C 语言库,你不仅有上述穿插编译的所有问题,你还必须确保你所依赖的 C 语言代码在 Go 反对的新平台上牢靠地工作 — 而且你必须在 C/Go 混合语言为你提供的无限调试能力的状况下做到这一点。

你失去了对所有工具的拜访权

Go 有很好的工具;咱们有 race detector、用于剖析代码的 pprof、覆盖率、含糊测试和源代码剖析工具。但 这些工具都不能在 cgo 中起到作用(也就是没法排查)。

相同,像 valgrind 这样优良的工具并不理解 Go 的调用约定或堆栈布局。在这一点上,Ian Lance Taylor 的工作是整合 clang 的内存净化器来调试 C 端的悬空指针,这对 Go 1.6 中的 cgo 用户有益处。

将 Go 代码和 C 代码联合起来的后果是两个世界的交叉点,而不是结合点;C 的内存平安和 Go 程序的调试性。但失去了许多外围工具的应用空间。

性能将始终是一个问题

C 代码和 Go 代码生存在两个不同的世界里,cgo 穿梭了它们之间的边界,这种转换不是收费的。而且取决于它在你的代码中存在的地位,其老本可能是无关紧要的,也可能是微小的。

C 对 Go 的调用常规或可增长的堆栈无所不知,所以对 C 代码的调用必须记录 goroutine 堆栈的所有细节,切换到 C 堆栈,并运行 C 代码,而 C 代码对它是如何被调用的,或负责程序的更大的 Go 运行时无所不知。

偏心地说,Go 对 C 的世界也无所不知。这就是为什么随着工夫的推移,两者之间的数据传递规定变得越来越繁琐,因为编译器越来越长于发现不再被认为是无效的堆栈数据,而垃圾回收器也越来越长于对堆进行同样的解决。

如果在 C 语言世界中呈现故障,Go 代码必须复原足够的状态,至多要打印出堆栈跟踪并洁净地退出程序,而不是把外围文件的信息都裸露进去。

治理这种跨调用堆栈的过渡,尤其是波及到信号、线程和回调的状况下,是不容易的(Ian Lance Taylor 在 Go 1.6 中也做了大量的工作来改善信号处理与 C 的互操作性)。

归根结底,C 语言和 Go 语言之间的转换是不容易的,相互对对方都一户无知,会有显著的性能开销

C 语言发号施令,而不是你的代码

你用哪种语言编写绑定或包装 C 代码并不重要;Python、应用 JNI 的 Java、应用 libFFI 的一些语言,或者通过 cgo 的 Go;这是 C 的世界,你只是生存在其中。

Go 代码和 C 代码必须就如何共享地址空间、信号处理程序和线程 TLS 槽等资源达成统一 — 我说的统一,实际上是指 Go 必须围绕 C 代码的假如发展工作。C 代码能够假如它总是在一个线程上运行,或者基本没有筹备好在多线程环境下工作。

你不是在写一个应用 C 库的逻辑的 Go 程序,是在写一个必须与互不可控的 C 代码共存的 Go 程序,这个 C 代码很难被取代,在会谈中占上风,而且不关怀你的问题。

部署变得更加简单

任何对一般观众的 Go 演讲都会蕴含至多一张带有这些文字的幻灯片:Single, static binary(繁多的、动态的二进制)。

这是 Go 的一张王牌,使其成为远离虚拟机和运行时治理的典型代表。应用 cgo,你就放弃了这一点,放弃了 Go 的劣势区域。

依据你的环境,你可能会把你的 Go 我的项目编译成 deb 或 rpm,并且假如你的其余依赖项也被打包了,把它们作为装置依赖项退出,把问题推给操作系统的软件包管理器。但这对以前像 go build && scp 那样间接的构建和部署过程来说,是有几个重大的变动。

齐全动态地编译 Go 程序是可能的,但这绝不是简略的,这表明在我的项目中退出 cgo 的影响会波及整个构建和部署的生命周期。

理智的抉择

说白了,我并不是说你不应该应用 cgo。然而在你做这个设计前,请认真思考你将会放弃的 Go 的许多品质。

须要思考分明得失,再思考是否值得你这么去做。

文章继续更新,能够微信搜【脑子进煎鱼了】浏览,本文 GitHub github.com/eddycjy/blog 已收录,学习 Go 语言能够看 Go 学习地图和路线,欢送 Star 催更。

Go 图书系列

  • Go 语言入门系列:初探 Go 我的项目实战
  • Go 语言编程之旅:深刻用 Go 做我的项目
  • Go 语言设计哲学:理解 Go 的为什么和设计思考
  • Go 语言进阶之旅:进一步深刻 Go 源码

更多浏览

  • goto 语句让 Go 代码变成意大利面条?
  • Go 只会 if err != nil?这是不对的,分享这些优雅的解决姿态给你!
正文完
 0