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)
}
相关推荐
2401_895521342 小时前
SpringBoot Maven快速上手
spring boot·后端·maven
disgare2 小时前
关于 spring 工程中添加 traceID 实践
java·后端·spring
ictI CABL2 小时前
Spring Boot与MyBatis
spring boot·后端·mybatis
小江的记录本4 小时前
【Linux】《Linux常用命令汇总表》
linux·运维·服务器·前端·windows·后端·macos
yhole7 小时前
springboot三层架构详细讲解
spring boot·后端·架构
香香甜甜的辣椒炒肉7 小时前
Spring(1)基本概念+开发的基本步骤
java·后端·spring
@atweiwei7 小时前
深入解析gRPC服务发现机制
微服务·云原生·rpc·go·服务发现·consul
白毛大侠8 小时前
Go Goroutine 与用户态是进程级
开发语言·后端·golang
ForteScarlet8 小时前
从 Kotlin 编译器 API 的变化开始: 2.3.20
android·开发语言·后端·ios·开源·kotlin
大阿明8 小时前
SpringBoot - Cookie & Session 用户登录及登录状态保持功能实现
java·spring boot·后端