前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >初识 CGO - 利用 CGO 使用 C++ STL

初识 CGO - 利用 CGO 使用 C++ STL

作者头像
LinkinStar
发布2022-09-01 14:47:19
6010
发布2022-09-01 14:47:19
举报
文章被收录于专栏:LinkinStar's BlogLinkinStar's Blog

之前我也了解过 CGO 相关的知识,但是当时给我的印象全部都是 “CGO 性能差” “完全没有必要,实际根本用不到”,但是这次听了大佬的一些分享发现 CGO 其实就是黑科技啊,有了它你在使用 go 的时候有了更多的想象力。本文将带你初步了解和使用 CGO,本文只是抛砖头,因为有关 CGO 的文档其实蛮少的,在其中也有很多坑,所以今天来踩一次,不知道会不会留下什么坑….

有了 CGO,Go 就有机会继承 C/C++近半个世纪的遗产 by 曹大

CGO 使用案例分享

首先来看一下最近我看到使用 CGO 的两个案例

案例 1 mosn

mosn
mosn

https://github.com/mosn/mosn

其中 mosn 通过 CGO 的能力,想办法在 envoy 后面加了一层,使得其底层网络具备 C 同等的处理能力的同时,又能使上层业务可高效复用 mosn 的处理能力及 go 的高开发效率。

案例 2 主动式缓存

这个 GopherChina 上一个学而思网校的分享,主要讲的是如何设计一个主动式内存缓存,其中提到了 Go 的 GC 导致当有大量内存缓存的时候,对象数量过多,GC 扫描的时间会很长,所以将缓存对象存储到 C 中,然后利用 CGO 去访问缓存的对象,因为当对象在 C 中的时候就不参与 GC 了。当时我听到这个思路的时候也是觉得有点意思,原来 CGO 还可以有这样的操作。

理论存在,实践开始

开胃小菜

首先先来一个简单 hello world 让你没有用过 CGO 的同学来体验一下

代码语言:javascript
复制
package main

//#include <stdio.h>
import "C"

func main() {
    C.puts(C.CString("Hello, World\n"))
}

非常简单,这里有以下几点

  • import C,有了它我们就能通过 C. 来使用 C 的一些方法,还引入了 <stdio.h>
  • 我们通过 C.CString 将 go 的字符串转换为了 c 的 “字符串”
  • 调用 C 里面的 puts 函数打印了这个字符串

就是这么简单,一个 CGO 的代码就完成了,有了它你是不是觉得其实 CGO 很简单,可以为所欲为了?NoNo 其实还有下面几点需要注意

注意点
  1. 类型转换:Golang 里面类型不能拿出来直接给 C 使用,因为底层的存储方式不同,所以必须通过 C.CString 等类似的方法进行转换;同样的,C 返回的类型也无法在 Go 中直接使用,也需要做一次转换,如通过 C.GoString 将 c 的 *char 转换为 go 的 string
  2. 内存:C 是没有 GC 的,所以 C 的内存需要手动管理,比如这里构造的字符串,在 C 里面是需要手动释放的,通过 C.free(unsafe.Pointer(s)) 可以进行 free;当然,反过来,当 C 要访问 go 的内存的时候也需要注意,Go 是有 GC 的,而 Go 的 Gc 是不知道当前这个对象在 C 里面是否还有在使用的,所以如果使用不当,C 中访问 go 的对象,这个对象可能已经被 GC 了
  3. 性能损失:因为 Go 和 C 有着不同的内存模型和函数调用规约,所以显然在使用 CGO 的时候需要栈的切换工作,那么势必带来这性能的损失

其他细节可以还查看 https://golang.org/cmd/cgo/ https://golang.org/src/cmd/cgo/doc.go

正餐

基于我之前听到的分享案例二,主动式缓存,它想办法在 C 里面开辟了一片新天地,让它绕过了 GO 的 GC 扫描,于是我就想着实践一下,搞一个最小 demo 看看。

目标
  • 在 C 里面搞一个 map 当做缓存
  • Go 通过 CGO 去访问这个 map 进行操作

然后之前写 C++ 的时候就经常用到 STL 库嘛,那里面的 map 自然是耳熟能详,所以就想到了如果我能想办法搞定这个 STL 的库势必就能实现这个 demo 了,理论存在,实践开始。

my_map.h
代码语言:javascript
复制
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 里面你需要首先给出这个函数的定义,才能在下面使用这个函数并且实现它,所以就需要定这个。

my_map.cpp
代码语言:javascript
复制
#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 然后对它进行三个操作的实现,其中需要注点的是:

  1. STL 库里面的 map 实现是红黑树,有序,这里是偷懒,如果没有必要的话,需要 hashmap 的话可以使用 unordered_map
  2. 这里 using namespace std; 也是偷懒,我不想每个都写一遍 std:: 懂的都懂
  3. 这里使用 char* 作为入参是因为将 go 的字符串转换过来的时候是这个类型
main.go
代码语言:javascript
复制
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 对应的内存,输出:

代码语言:javascript
复制
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 程序的一个中间态,我觉得它会一直存在,给我们带来更多的黑魔法。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-07-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • CGO 使用案例分享
    • 案例 1 mosn
      • 案例 2 主动式缓存
      • 理论存在,实践开始
        • 开胃小菜
          • 注意点
        • 正餐
          • 目标
          • my_map.h
          • my_map.cpp
          • main.go
      • 总结与延伸
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档