go语言中一些用法

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 本文详细解析了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

相关文章
|
1天前
|
存储 Go 索引
go语言使用for循环遍历
go语言使用for循环遍历
16 7
|
4天前
|
存储 Go
go语言 遍历映射(map)
go语言 遍历映射(map)
18 2
|
5天前
|
Go 调度 开发者
Go语言中的并发编程:深入理解goroutines和channels####
本文旨在探讨Go语言中并发编程的核心概念——goroutines和channels。通过分析它们的工作原理、使用场景以及最佳实践,帮助开发者更好地理解和运用这两种强大的工具来构建高效、可扩展的应用程序。文章还将涵盖一些常见的陷阱和解决方案,以确保在实际应用中能够避免潜在的问题。 ####
|
5天前
|
测试技术 Go 索引
go语言使用 range 关键字遍历
go语言使用 range 关键字遍历
14 3
|
5天前
|
测试技术 Go 索引
go语言通过 for 循环遍历
go语言通过 for 循环遍历
16 3
|
7天前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
6天前
|
Go 索引
go语言按字符(Rune)遍历
go语言按字符(Rune)遍历
21 3
|
10天前
|
Go API 数据库
Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
本文介绍了 Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
34 4
|
10天前
|
缓存 监控 前端开发
在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统
本文深入探讨了在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统。
45 1
|
8天前
|
存储 Go PHP
Go语言中的加解密利器:go-crypto库全解析
在软件开发中,数据安全和隐私保护至关重要。`go-crypto` 是一个专为 Golang 设计的加密解密工具库,支持 AES 和 RSA 等加密算法,帮助开发者轻松实现数据的加密和解密,保障数据传输和存储的安全性。本文将详细介绍 `go-crypto` 的安装、特性及应用实例。
28 0