如果实现 Go 调用 C
- 用注释写一段
C
代码 - 下面紧跟着
import "C"
,注释会变颜色 - 用
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_sum
在 main.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)); }
原理:
- 在内存中开辟一个结构体
- 结构体中含有参数和返回值
- 结构体地址传入
C
方法 C
方法将结果写入返回值的位置
go
的调度器需要配合:
- 协程需要抢占式调度
- 线程在运行时协程时,会不断的切换协程
- 进入
C
程序之后,调度器无法抢占协程- 一旦进入
C
程序之后,go
的线程就没法动弹了,直到C
方法运行结束
- 一旦进入
- 调度器停止对此协程的调度
协程栈需要切换:
C
的栈用的是系统栈,不受go runtime
管理- 进入
C
时,需要将当前栈切换到线程的系统栈上
cgo
的优点:cgo
可以让 go
调用现成的 C
实现
cgo
的缺点
cgo
限制了go
语言的跨平台特性cgo
并不能提高go
语言的性能
defer 底层原理是怎样的
defer
有两种情况:
- 协程记录
defer
信息,函数退出时调用 - 将
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) }