go语言之-cgo

最近由于工作需要,由于选型帧同步,服务器需要跑客户端代码,我们后端是go语言,客户端是c++

记录一下在这个过程中遇到的问题,和解决问题思路。与最后的成果。

  1. 首先学习cgo
    学习一个组件很简单,学习cgo的基础就可以,既然我是使用,并不需要研究cgo的多么深。

cgo需要在go文件中加入import "C",并且需要在import "C"上面紧贴着写上自己的c代码

例如

package main

/*
#cgo LDFLAGS: -L -lc -ldl
#include 
#include 
#include 
#include "library.h" //非标准c头文件,所以用引号
*/
import "C"

这里记录一下 #cgo LDFLAGS: 的作用吧,这个的作用就是设置gcc的一些参数。-L和gcc中的LD_LIBRARY是一样的东西,-l后面接上动态库。
假如说,在同级目录下,有一个libaaa.so 那么我需要这样设置

#cgo LDFLAGS: -L ./ -laaa 

这里我加上了-ldl 我要加载操作系统中某些库,因为后面我用到了dlopen动态加载so库
如果你出现过dlopen' ‘dlsym’ undefined reference 你可以加上-ldl 如果过你还没找到,你就去找出现没找到的包所在的系统位置,然后查看系统环境变量LD_LIBRARY_PATH里面是否有当前目录就可以。(-ldl要放在最后面)

接下来,我们就要用了,我写了一个library.h与我的go文件相同的位置


image.png

这样,我直接写#include"文件名"就可以了

在这里简单介绍下cgo的代码使用

cgo封装了c中所有结构体。有很多文章都说过他的类型转换问题了。
其实很简单,直接C.对应的类型就可以
例如

// cgo.go
    cs := C.CString("XXX")
    defer C.free(unsafe.Pointer(cs))
    C.InitConfigData( cs)
// library.c
void Init(char* resPath)
{  
    。。。
}

这样是直接可以调用的
这里需要注意的是,go的内存是有垃圾回收的,但是并不回收c的内存属性。所以,这里所有的入参必须是C的类型。
而且初始化完了必须要加defer()自己手动释放。

cgo调用c++

这里有很多方式调用,我选用的是cgo-->c-->c++的路线
因为,的c++代码是动态库,由于客户端程序员不给我封装c的 .h文件,(因为工作量太大,引用了很多c++std库的东西,所以要全部封出来还需要很大的代码量)
我自己写的这个library.c就是从程序里依赖c++动态库,动态加载到内存中。
具体不详细写了,直接上代码

typedef  struct tagUServerMgr* (*GetInstanceFunc)();
static GetInstanceFunc getInstance = NULL;

// 省略好多行

        //打开动态链接库
    handle = dlopen(lib_path, RTLD_NOW);
    if (!handle) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    //清除之前存在的错误
    dlerror();

    getInstance = (GetInstanceFunc)dlsym(handle, "GetInstance");
    if ((error = dlerror()) != NULL)  {
        fprintf(stderr, "%s\n", error);
        exit(EXIT_FAILURE);
    }
    if (getInstance==NULL){
        exit(EXIT_FAILURE);
    }
// 省略好多行

这里代码,我就是用RTLD_NOW的方式加载动态库。然后获取里面的getInstance方法。之后,我go代码直接可以C.GetInstance()就可以调用。我个人觉得挺方便的。

cgo的性能测试,

在客户端跑帧的时候,测试过cgo的性能。
从go程序调用,到c方法入口,期间传基础数据类型。测试下来平均调用一次消耗0.3ms。如果你的业务不需要1毫秒一下的效率,可以放心大胆的使用。对复杂数据类型解析,会慢,但是没有明显变化,在我的业务下,转换一个1kb的数据。大约在0.5ms调用一次,包括入参出参时间。

cgo遇到的问题

问题:在测试中发现,当我go程序sleep后,cgo调用的c方法效率会明显下降。但是如果不sleep会效率能够达到需求。由于,业务要求是帧同步,所以我用sleep来卡ms时间
(经过了与客户端半天的争吵,客户端写了个自测程序,发现没有问题所以我怀疑是我这边的问题)
解决思路:可能是time.sleep的操作。
解决:更换成为time.tinker等time下其他的时间控制
结果:相同结果,只要每次调用中间sleep30ms 就会变慢。
解决思路:如果只sleep1ms会不会变慢。
解决:分别测试了1ms,2ms,5ms
结果:发现1ms也会变慢。问题清晰了许多,似乎就是sleep的问题
解决思路:如果用卡时间戳的概念,模拟sleep的睡的时间是否能够满足需求,不断地for循环,每次都判断是否过了30ms,过了,就执行一次,没过就等下一次判断。
解决:写了个for的例子。测试下
结果:发现的确快了,是time.sleep的问题

  • 目前为止:代码层面已经解决了,可能之后业务中也是使用for但是for有好几个不好的地方,
    1. 只要跑就是cpu打满。在go中,会默认分配1个携程可以沾满1个cpu。
    2. 会出现很多空转for,因为我每次调用帧更新最小是1ms,如果一个cpu一直在工作,那么最多处理1000帧更新请求,那么一个玩家是一秒需要跑30帧,在这33个玩家正正好好不冲突的情况下,全部没有网络延迟,没有io限制,等等,1个cpu最多支持33个玩家同时玩。但是这33不是不冲突的情况下,假如说,某几纳秒所有人都在等下一个帧时间到来,那么cpu就会空转。浪费cpu资源。
    3. 1个玩家也会沾满cpu所有资源

这些问题,可能留到后期优化了,各位有什么好的建议可以可以私信我。

接下来查看go中的sleep是否对接下来的程序执行效率会变慢。

查看sleep的源码发现,sleep是采用系统中断来唤醒睡着的程序。sleep这行代码,做了比较重要的事情是

  1. 更改了系统终端表,操作系统会不断查看系统终端表来发中断,从而唤醒任务
  2. 把当前线程从cpu任务队列拿出,意思是当前线程资源会从cpu中拿走。这也就是为什么sleep完了后会执行变慢,可能会有加载数据这段时间吧。

你可能感兴趣的:(go语言之-cgo)