go语言中一些用法

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: 本文详细解析了Go语言如何通过C语言扩展其功能。主要包括:使用注释书写C代码、通过Go调用C中的函数示例及背后的工作原理;解释了Go如何通过内存结构体传递参数并接收返回值,以及Go调度器如何处理C代码执行期间的控制流问题;讨论了使用cgo的优势与局限性;最后介绍了Go语言中`defer`关键字的工作机制,并通过具体示例展示了反射在动态调用函数中的应用。

如果实现 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. 协程需要抢占式调度
  • 线程在运行时协程时,会不断的切换协程
  1. 进入 C 程序之后,调度器无法抢占协程
  • 一旦进入 C 程序之后,go 的线程就没法动弹了,直到 C 方法运行结束
  1. 调度器停止对此协程的调度

协程栈需要切换:

  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)
}


转载来源:https://juejin.cn/post/7388022210856239116

相关文章
|
2天前
|
程序员 Go PHP
为什么大部分的 PHP 程序员转不了 Go 语言?
【9月更文挑战第8天】大部分 PHP 程序员难以转向 Go 语言,主要因为:一、编程习惯与思维方式差异,如语法风格和编程范式;二、学习成本高,需掌握新知识体系且面临项目压力;三、职业发展考量,现有技能价值及市场需求不确定性。学习新语言虽有挑战,但对拓宽职业道路至关重要。
24 10
|
2天前
|
算法 程序员 Go
PHP 程序员学会了 Go 语言就能唬住面试官吗?
【9月更文挑战第8天】学会Go语言可提升PHP程序员的面试印象,但不足以 solely “唬住” 面试官。学习新语言能展现学习能力、拓宽技术视野,并增加就业机会。然而,实际项目经验、深入理解语言特性和综合能力更为关键。全面展示这些方面才能真正提升面试成功率。
21 10
|
2天前
|
编译器 Go
go语言学习记录(关于一些奇怪的疑问)有别于其他编程语言
本文探讨了Go语言中的常量概念,特别是特殊常量iota的使用方法及其自动递增特性。同时,文中还提到了在声明常量时,后续常量可沿用前一个值的特点,以及在遍历map时可能遇到的非顺序打印问题。
|
6天前
|
安全 大数据 Go
深入探索Go语言并发编程:Goroutines与Channels的实战应用
在当今高性能、高并发的应用需求下,Go语言以其独特的并发模型——Goroutines和Channels,成为了众多开发者眼中的璀璨明星。本文不仅阐述了Goroutines作为轻量级线程的优势,还深入剖析了Channels作为Goroutines间通信的桥梁,如何优雅地解决并发编程中的复杂问题。通过实战案例,我们将展示如何利用这些特性构建高效、可扩展的并发系统,同时探讨并发编程中常见的陷阱与最佳实践,为读者打开Go语言并发编程的广阔视野。
|
3天前
|
存储 Shell Go
Go语言结构体和元组全面解析
Go语言结构体和元组全面解析
|
9天前
|
Go
golang语言之go常用命令
这篇文章列出了常用的Go语言命令,如`go run`、`go install`、`go build`、`go help`、`go get`、`go mod`、`go test`、`go tool`、`go vet`、`go fmt`、`go doc`、`go version`和`go env`,以及它们的基本用法和功能。
20 6
|
8天前
|
存储 Go
Golang语言基于go module方式管理包(package)
这篇文章详细介绍了Golang语言中基于go module方式管理包(package)的方法,包括Go Modules的发展历史、go module的介绍、常用命令和操作步骤,并通过代码示例展示了如何初始化项目、引入第三方包、组织代码结构以及运行测试。
16 3
|
10天前
|
缓存 安全 Java
如何利用Go语言提升微服务架构的性能
在当今的软件开发中,微服务架构逐渐成为主流选择,它通过将应用程序拆分为多个小服务来提升灵活性和可维护性。然而,如何确保这些微服务高效且稳定地运行是一个关键问题。Go语言,以其高效的并发处理能力和简洁的语法,成为解决这一问题的理想工具。本文将探讨如何通过Go语言优化微服务架构的性能,包括高效的并发编程、内存管理技巧以及如何利用Go生态系统中的工具来提升服务的响应速度和资源利用率。
|
11天前
|
Rust Linux Go
Rust/Go语言学习
Rust/Go语言学习
|
12天前
|
Java 数据库连接 数据库
携手前行:在Java世界中深入挖掘Hibernate与JPA的协同效应
【8月更文挑战第31天】Java持久化API(JPA)是一种Java规范,为数据库数据持久化提供对象关系映射(ORM)方法。JPA定义了实体类与数据库表的映射及数据查询和事务控制方式,确保不同实现间的兼容性。Hibernate是JPA规范的一种实现,提供了二级缓存、延迟加载等丰富特性,提升应用性能和可维护性。通过结合JPA和Hibernate,开发者能编写符合规范且具有高度可移植性的代码,并利用Hibernate的额外功能优化数据持久化操作。
28 0