go 中一些其他的用法

如果实现 Go 调用 C

  1. 用注释写一段 C 代码
  2. 下面紧跟着 import "C",注释会变颜色
  3. C 包调用 sum 函数
go 复制代码
package main

/*
int sum(int a, int b) {
  return a+b;
}
*/
import "C"
import "fmt"

func main() {
  fmt.Println(C.sum(1, 2))
}

go 语言是如何实现调用 C 代码的呢?

运行 go tool cgo main.go 会在当前目录生成 _obj 的文件夹,就可以看到生成的 C 代码了

main.go 文件会被编译成 main.cgo1.go 文件,C.sum 函数会被转换成 _Cfunc_sum 函数

go 复制代码
println(( /*line :11:10*/_Cfunc_sum /*line :11:14*/)(1, 2))

_Cfunc_sum 函数在 _cgo_gotypes.go 文件中定义

go 复制代码
//go:cgo_unsafe_args
func _Cfunc_sum(p0 _Ctype_int, p1 _Ctype_int) (r1 _Ctype_int) {
  _cgo_runtime_cgocall(_cgo_db952ddc3181_Cfunc_sum, uintptr(unsafe.Pointer(&p0)))
  if _Cgo_always_false {
    _Cgo_use(p0)
    _Cgo_use(p1)
  }
  return
}

_Cfunc_sum 函数调用 _cgo_db952ddc3181_Cfunc_sum 方法

_cgo_db952ddc3181_Cfunc_summain.cgo2.c 文件中定义

c 复制代码
void
_cgo_db952ddc3181_Cfunc_sum(void *v)
{
  struct {
    int p0;
    int p1;
    int r;
    char __pad12[4];
  } __attribute__((__packed__)) *_cgo_a = v;
  char *_cgo_stktop = _cgo_topofstack();
  __typeof__(_cgo_a->r) _cgo_r;
  _cgo_tsan_acquire();
  _cgo_r = sum(_cgo_a->p0, _cgo_a->p1);
  _cgo_tsan_release();
  _cgo_a = (void*)((char*)_cgo_a + (_cgo_topofstack() - _cgo_stktop));
  _cgo_a->r = _cgo_r;
  _cgo_msan_write(&_cgo_a->r, sizeof(_cgo_a->r));
}

原理:

  1. 在内存中开辟一个结构体
  2. 结构体中含有参数和返回值
  3. 结构体地址传入 C 方法
  4. C 方法将结果写入返回值的位置

go 的调度器需要配合:

  1. 协程需要抢占式调度
    • 线程在运行时协程时,会不断的切换协程
  2. 进入 C 程序之后,调度器无法抢占协程
    • 一旦进入 C 程序之后,go 的线程就没法动弹了,直到 C 方法运行结束
  3. 调度器停止对此协程的调度

协程栈需要切换:

  1. C 的栈用的是系统栈,不受 go runtime 管理
  2. 进入 C 时,需要将当前栈切换到线程的系统栈上

cgo 的优点:cgo 可以让 go 调用现成的 C 实现

cgo 的缺点

  • cgo 限制了 go 语言的跨平台特性
  • cgo 并不能提高 go 语言的性能

defer 底层原理是怎样的

defer 有两种情况:

  1. 协程记录 defer 信息,函数退出时调用
  2. defer 代码直接编译进函数尾部
go 复制代码
func a() {
  fmt.Println("1")
}

func main() {
  defer a()
}

通过 go build -gcflags -S main.go 查看汇编

s 复制代码
0x001a 00026 (/project/tools/limit-go/main.go:11)    LEAQ    main.a·f(SB), AX
0x0021 00033 (/project/tools/limit-go/main.go:11)    MOVQ    AX, main..autotmp_1+8(SP)
0x0026 00038 (/project/tools/limit-go/main.go:12)    MOVB    $0, main..autotmp_0+7(SP)
0x002b 00043 (/project/tools/limit-go/main.go:12)    PCDATA  $1, $1
0x002b 00043 (/project/tools/limit-go/main.go:12)    CALL    main.a(SB)
0x0030 00048 (/project/tools/limit-go/main.go:12)    ADDQ    $16, SP
0x0034 00052 (/project/tools/limit-go/main.go:12)    POPQ    BP
0x0035 00053 (/project/tools/limit-go/main.go:12)    RET
0x0036 00054 (/project/tools/limit-go/main.go:12)    CALL    runtime.deferreturn(SB)
0x003b 00059 (/project/tools/limit-go/main.go:12)    ADDQ    $16, SP
0x003f 00063 (/project/tools/limit-go/main.go:12)    POPQ    BP
0x0040 00064 (/project/tools/limit-go/main.go:12)    RET
0x0041 00065 (/project/tools/limit-go/main.go:12)    NOP

通过汇编我们可以看到,go 在编译时:

  • 11 行时遇到 defer,将 defer 的信息记录下来
  • 在函数结束之前,也就是第 12 行, 它调用了 runtime.deferreturn 方法

反射

go 复制代码
func Add(a, b int) int {
  return a + b
}

func Sub(a, b int) int {
  return a - b
}

func Call(f func(a int, b int) int) {
  v := reflect.ValueOf(f)
  if v.Kind() != reflect.Func {
    return
  }
  argv := make([]reflect.Value, 2)
  argv[0] = reflect.ValueOf(1)
  argv[1] = reflect.ValueOf(2)
  result := v.Call(argv)
  fmt.Println(result[0].Int())
}
func main() {
  Call(Add)
  Call(Sub)
}
相关推荐
阳光是sunny42 分钟前
别再被 worktree 绕晕了!AI 编程时代你必须掌握的 Git 隔离神器
前端·人工智能·后端
万少2 小时前
万少的博客 - 技术分享与解决方案
前端·javascript·后端
咖啡八杯2 小时前
GoF设计模式——备忘录模式
java·后端·spring·设计模式
苍何2 小时前
腾讯再放大招,企微 Agent 大圆开启内测
后端
ethantan2 小时前
一篇讲解AI Agent 组成:像人一样思考的智能体
人工智能·后端·程序员
Cosolar4 小时前
vLLM 生产级部署完全指南
人工智能·后端·架构
IT_陈寒5 小时前
垃圾回收器选错了,我的Java服务内存炸了
前端·人工智能·后端
用户8356290780516 小时前
使用 Python 在 PDF 中创建与管理书签
后端·python
Nturmoils6 小时前
字段太多看不全,ksql 的展开模式和输出控制怎么用
数据库·后端
大志说编程6 小时前
Agent面试真题06: 十分钟带你快速掌握Agent记忆管理高频面试题(附详细答案)
后端·面试·ai编程