当前位置: 首页 > 后端技术 > Java

在Go中使用CGO?这7个问题你要注意!

时间:2023-04-01 20:12:30 Java

大家好,我是炸鱼。今天要跟大家分享的是,Cgo并不是Go谚语中的Go。原文章同名,略有改动。作者是@DaveCheney。以下“本人”均指原作者。借用JWZ的一句话:有些人在遇到问题的时候会想“我知道,我会用cgo(来解决)”。使用cgo之后,他们会遇到两个新的问题。什么是CGO?Cgo是一项了不起的技术,它允许Go程序与C语言库进行互操作,这是一个非常有用的功能。没有它,Go就不会成为今天的样子。cgo是在Android和iOS上运行Go程序的关键。过度使用我个人认为在Go项目中cgo被过度使用,当面对在Go中重新实现一大块C代码时,程序员会选择使用cgo来包装库,认为这是一个更容易解决的问题。但我认为这是一种糟糕的选择行为。显然,在某些情况下,cgo是不可避免的,尤其是当您必须与图形驱动程序或窗口系统进行互操作时,它们只能作为二进制blob提供。在这些情况下,cgo的使用证明了其权衡取舍的合理性,远远少于许多人愿意承认的。以下是您在将Go项目基于cgo库时可能不知道的权衡的不完整列表。你需要考虑一下。更长的构建时间当您在Go包中导入“C”时,gobuild需要做更多的工作来构建您的代码。构建你的包不再是简单的将范围内所有.go文件的列表传递给go工具编译的单一调用,而是包含以下工作项:需要调用cgo工具来生成相关的CtoGo和Go到C代码。系统中的C编译器对包中的每个C文件进行调用处理。各个编译单元合并为一个.o文件。生成的.o文件将通过系统的链接器来更正它引用的共享对象。所有这些工作都会在您每次编译或测试您的包时发生,如果您正在积极处理该包,情况通常就是这样。Go工具将在可能的情况下并行执行此工作(包括完全重建所有C代码),并且包的编译时间会增加,并且会相应增加。您还需要在所有主要平台上调试您的C语言代码,以避免因兼容性而导致编译失败。复杂构建Go的目标之一是生成一种构建过程是自描述的语言;您的程序的源代码包含足够的信息供工具构建项目。这并不是说使用Makefiles来自动化你的构建工作流是不好的,但是在你的项目中引入cgo之前,你可能不需要除了go工具之外的任何东西来构建和测试。导入cgo后,需要设置所有环境变量,跟踪可能安装在奇怪地方的共享对象和头文件。另请注意,Go支持许多平台,而cgo不支持。因此,您将不得不花一些时间为您的Windows用户想出一个解决方案。现在你的用户必须安装一个C编译器,而不仅仅是一个Go编译器。他们还要安装你项目依赖的C语言库,你也要承担这个技术支持的费用。交叉编译被抛在了窗外Go对交叉编译的支持是一流的。从Go1.5开始,您可以通过Go项目网站上的官方安装程序支持从任何平台到任何其他平台的交叉编译。默认情况下,交叉编译时禁用cgo。通常,如果你的项目是纯Go,这不是问题。当你混入对C库的依赖时,你要么放弃交叉编译你的包,要么你必须投入时间为所有目标寻找和维护一个交叉编译的C工具链,以便交叉编译。Go支持的平台数量在不断增加。Go1.5增加了对64位ARM和PowerPC的支持。Go1.6添加了对64位MIPS的支持,而IBM的s390架构被吹捧为Go1.7。RISC-V正在开发中。如果你的产品依赖于一个C库,你不仅会遇到上述交叉编译的所有问题,你还必须确保你所依赖的C代码在Go支持的新平台上可靠地工作——而且你必须使用C/Go在没有混合语言给你的有限调试能力的情况下做到这一点。你无法访问Go拥有的所有工具;我们有竞争检测器、用于分析代码的pprof、覆盖率、模糊测试和源代码分析工具。但是这些工具在cgo中都不起作用(即无法检查)。相比之下,像valgrind这样的好工具不了解Go的调用约定或堆栈布局。在这一点上,IanLanceTaylor集成clang的内存清理器以调试C端的悬挂指针的工作对Go1.6中的cgo用户来说是有好处的。Go代码和C代码结合的结果是两个世界的交集,而不是交汇点;C的内存安全和Go程序的可调试性。但是失去了许多核心工具的空间。性能永远是一个问题。C代码和Go代码生活在两个不同的世界,cgo跨越了它们之间的边界,并且转换不是免费的。根据它在代码中的位置,成本可能微不足道或巨大。C对Go的调用约定或可增长堆栈一无所知,因此对C代码的调用必须记录goroutine堆栈的所有细节,切换到C堆栈,并运行C代码,它不知道它是如何调用的,或者负责程序的更大的Go运行时一无所知。公平地说,Go对C世界也一无所知。这就是为什么随着时间的推移,在两者之间传递数据的规则变得越来越繁琐,因为编译器越来越善于发现不再被认为有效的堆栈数据,而垃圾收集器也越来越善于对堆做同样的事情。如果C世界出现问题,Go代码必须恢复足够的状态以至少打印出堆栈跟踪并干净地退出程序,而不会暴露核心文件信息。跨调用堆栈管理这种转换,尤其是涉及信号、线程和回调的地方,并不容易(IanLanceTaylor在Go1.6中也做了很多工作来改进与C的信号处理互操作性)。说到底,C语言和Go语言的转换并不容易。他们互不相识,会有明显的性能开销。C语言做主,而不是您的代码。使用哪种语言编写绑定或包装C代码并不重要;Python,使用JNI的Java,一些使用libFFI的语言,或者通过cgo的Go;这是C的世界,你就生活在其中。Go代码和C代码必须就如何共享地址空间、信号处理程序和线程TLS插槽等资源达成一致——当我说同意时,我的意思是Go必须围绕C代码的假设进行工作。C代码可以假设它总是在一个线程上运行,或者根本不准备在多线程环境中工作。你不是在写一个使用C库逻辑的Go程序,你在写一个Go程序必须与相互不可控的C代码共存,难以替代,在谈判中占上风,并且不关心关于你的问题。部署变得更加复杂任何面向普通观众的Go演示都将包含至少一张带有以下文本的幻灯片:单一静态二进制文件。这是Go的王牌之一,使其成为摆脱虚拟机和运行时管理的典范。有了cgo,你就放弃这个,放弃Go的优势区域。根据您的环境,您可能会将您的Go项目编译为deb或rpm,并假设您的其他依赖项也已打包,将它们添加为安装依赖项,将问题推送到操作系统的包管理器。但这是对之前简单的构建和部署过程(如gobuild&&scp)的一些重大更改。完全静态地编译Go程序是可能的,但绝非易事,这表明在项目中包含cgo的影响会波及整个构建和部署生命周期。明智地选择坦率地说,我并不是说你不应该使用cgo。但在进行此设计之前,请仔细考虑您将放弃的Go的许多特性。你需要把得失考虑清楚,再想想自己这样做是否值得。文章持续更新中。可以微信搜索【脑补炸鱼】阅读。本文已收录在GitHubgithub.com/eddycjy/blog中。学习Go语言可以看Go学习地图和路线。欢迎星星提醒。Go书系列Go语言入门系列:初探Go项目实战Go语言编程之旅:深入使用Go做项目Go语言设计哲学:理解Go的Why和DesignThinking语句把Go代码变成意大利面条?仅当err!=nil时才去?不对,把这些优雅的搬运姿势分享给你!