之前我也了解过 CGO 相关的知识,但是当时给我的印象全部都是 “CGO 性能差” “完全没有必要,实际根本用不到”,但是这次听了大佬的一些分享发现 CGO 其实就是黑科技啊,有了它你在使用 go 的时候有了更多的想象力。本文将带你初步了解和使用 CGO,本文只是抛砖头,因为有关 CGO 的文档其实蛮少的,在其中也有很多坑,所以今天来踩一次,不知道会不会留下什么坑….
有了 CGO,Go 就有机会继承 C/C++近半个世纪的遗产 by 曹大
首先来看一下最近我看到使用 CGO 的两个案例
其中 mosn 通过 CGO 的能力,想办法在 envoy 后面加了一层,使得其底层网络具备 C 同等的处理能力的同时,又能使上层业务可高效复用 mosn 的处理能力及 go 的高开发效率。
这个 GopherChina 上一个学而思网校的分享,主要讲的是如何设计一个主动式内存缓存,其中提到了 Go 的 GC 导致当有大量内存缓存的时候,对象数量过多,GC 扫描的时间会很长,所以将缓存对象存储到 C 中,然后利用 CGO 去访问缓存的对象,因为当对象在 C 中的时候就不参与 GC 了。当时我听到这个思路的时候也是觉得有点意思,原来 CGO 还可以有这样的操作。
首先先来一个简单 hello world 让你没有用过 CGO 的同学来体验一下
package main
//#include <stdio.h>
import "C"
func main() {
C.puts(C.CString("Hello, World\n"))
}
非常简单,这里有以下几点
就是这么简单,一个 CGO 的代码就完成了,有了它你是不是觉得其实 CGO 很简单,可以为所欲为了?NoNo 其实还有下面几点需要注意
其他细节可以还查看 https://golang.org/cmd/cgo/ https://golang.org/src/cmd/cgo/doc.go
基于我之前听到的分享案例二,主动式缓存,它想办法在 C 里面开辟了一片新天地,让它绕过了 GO 的 GC 扫描,于是我就想着实践一下,搞一个最小 demo 看看。
然后之前写 C++ 的时候就经常用到 STL 库嘛,那里面的 map 自然是耳熟能详,所以就想到了如果我能想办法搞定这个 STL 的库势必就能实现这个 demo 了,理论存在,实践开始。
void MmPut(const char* key, const char* value);
const char* MmGet(const char* key);
void MmDelete(const char* key);
首先定义一个头文件 my_map.h
,里面包含三个函数分别是 put,get,delete 对 map 的相关操作
这里解释一下,因为在 C 里面你需要首先给出这个函数的定义,才能在下面使用这个函数并且实现它,所以就需要定这个。
#include <string>
#include <map>
extern "C" {
#include "my_map.h"
using namespace std;
}
map<string, string> mm;
void MmPut(const char* key, const char* value) {
mm[key] = value;
}
const char* MmGet(const char* key) {
return mm[key].data();
}
void MmDelete(const char* key) {
mm.erase(key);
}
然后定义我们的具体方法 my_map.h
这里写的很粗糙,就直接定义了一个 map 然后对它进行三个操作的实现,其中需要注点的是:
using namespace std;
也是偷懒,我不想每个都写一遍 std::
懂的都懂char*
作为入参是因为将 go 的字符串转换过来的时候是这个类型package main
//void MmPut(const char* key, const char* value);
//const char* MmGet(const char* key);
//void MmDelete(const char* key);
/*
#include <stdlib.h>
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
Put2MyMap("author", "linkinstar")
s := GetFromMyMap("author")
fmt.Println("before delete:", s)
DeleteFromMyMap("author")
s = GetFromMyMap("author")
fmt.Println("after delete:", s)
}
func Put2MyMap(key, value string) {
k := C.CString(key)
v := C.CString(value)
defer func() {
C.free(unsafe.Pointer(k))
C.free(unsafe.Pointer(v))
}()
C.MmPut(k, v)
}
func GetFromMyMap(key string) (value string) {
k := C.CString(key)
defer func() {
C.free(unsafe.Pointer(k))
}()
v := C.MmGet(k)
value = C.GoString(v)
return value
}
func DeleteFromMyMap(key string) {
k := C.CString(key)
defer func() {
C.free(unsafe.Pointer(k))
}()
C.MmDelete(k)
}
最后就是我们的 GO 代码了,封装了一下三个方法,在 main 中测试一下,完成~
其中需要注意的是之前上面提到的手动 free 对应的内存,输出:
before delete: linkinstar
after delete:
至此我们就最简单的能通过 CGO 使用 STL 库了,那么相对应的,有了这个砖头,那么其他相关的 vector,set….你都可以使用了,甚至可以来个什么 algorithm 的 next_permutation 什么的,想想就有点刺激。
当然以上只是个 demo,如果需要真的当缓存来用,还有很多需要优化的地方,比如调用过程中减少 key,value 的拷贝,缓存的并发访问等等….. 也有库已经实现一个这样的 map,如果有需要可以尝试进行使用 https://github.com/glycerine/offheap
其实看着代码很容易,但是当我第一次写的时候碰到一堆的问题,一方面是 CGO 的资料不多,代码也不多,所以参考资料比较少,很多代码需要猜测怎么样写,基本上是照猫画虎,用过之后就好了很多了,基本上能知道大体的使用,剩下的就是细节了。
CGO 的使用前提还需要你对 C 有一定的了解,如果完全没有接触过,可能也会觉得比较困难。很期待主动式缓存那个框架实现的开源,这样可以巴拉巴拉它的代码看看它是
那么其实除了 STL 一个特别有意思的事情,就是 OC,没错 ObjectC。我们知道 Cocoa 是苹果官方 macOS 出的一个接口,那么其实可以通过 cgo 来调用其中的接口来做一些 macos 原生做的事情,这就非常有意思了。github 上其实有很多相关的库,这里就不再列举了。
https://coderwall.com/p/l9jr5a/accessing-cocoa-objective-c-from-go-with-cgo
https://github.com/alediaferia/gogoa
总的来说,CGO 就像一座桥,不仅让 Go 继承了 C 的遗产,而且连接更加广阔的空间,给了你更多的想象力。我觉得它并不是很多人所说的是 C++ 程序迁移到 Go 程序的一个中间态,我觉得它会一直存在,给我们带来更多的黑魔法。