Go语言高级编程

324课时
1.4K学过
8分

2. 第2章 CGO编程

引言

2.1 快速入门

2.1.1 最简CGO程序

2.1.2 基于C标准库函数输出字符串

2.1.3 使用自己的C函数

2.1.4 C代码的模块化

2.1.5 用Go重新实现C函数

2.1.6 面向C接口的Go编程

2.2 CGO基础

2.2.1 import "C"语句

2.2.2 #cgo语句

2.2.3 build tag 条件编译

2.3 类型转换

2.3.1 数值类型

2.3.2 Go 字符串和切片

2.3.3 结构体、联合、枚举类型

2.3.4 数组、字符串和切片

2.3.5 指针间的转换

2.3.6 数值和指针的转换

2.3.7 切片间的转换

2.4 函数调用

2.4.1 Go调用C函数

2.4.2 C函数的返回值

2.4.3 void函数的返回值

2.4.4 C调用Go导出函数

2.5 内部机制

2.5.1 CGO生成的中间文件

2.5.2 Go调用C函数

2.5.3 C调用Go函数

2.6 实战: 封装qsort

2.6.1 认识qsort函数

2.6.2 将qsort函数从Go包导出

2.6.3 改进:闭包函数作为比较函数

2.6.4 改进:消除用户对unsafe包的依赖

2.7 CGO内存模型

2.7.1 Go访问C内存

2.7.2 C临时访问传入的Go内存

2.7.3 C长期持有Go指针对象

2.7.4 导出C函数不能返回Go内存

2.8 C++ 类包装

2.8.1 C++ 类到 Go 语言对象

2.8.1.1 准备一个 C++ 类

2.8.1.2 用纯C函数接口封装 C++ 类

2.8.1.3 将纯C接口函数转为Go函数

2.8.1.4 包装为Go对象

2.8.2 Go 语言对象到 C++ 类

2.8.2.1 构造一个Go对象

2.8.2.2 导出C接口

2.8.2.3 封装C++对象

2.8.2.4 封装C++对象改进

2.8.3 彻底解放C++的this指针

2.9 静态库和动态库

2.9.1 使用C静态库

2.9.2 使用C动态库

2.9.3 导出C静态库

2.9.4 导出C动态库

2.9.5 导出非main包的函数

2.10 编译和链接参数

2.10.1 编译参数:CFLAGS/CPPFLAGS/CXXFLAGS

2.10.2 链接参数:LDFLAGS

2.10.3 pkg-config

2.10.4 go get 链

2.10.5 多个非main包中导出C函数

课程评价 (0)

请对课程作出评价:
0/300

学员评价

暂无精选评价
10分钟

1.5.6 基于Channel的通信

Channel通信是在Goroutine之间进行同步的主要方法。在无缓存的Channel上的每一次发送操作都有与其对应的接收操作相配对,发送和接收操作通常发生在不同的Goroutine上(在同一个Goroutine上执行2个操作很容易导致死锁)。无缓存的Channel上的发送操作总在对应的接收操作完成前发生.

var done = make(chan bool)
var msg string

func aGoroutine() {
	msg = "你好, 世界"
	done <- true
}

func main() {
	go aGoroutine()
	<-done
	println(msg)
}

可保证打印出“hello, world”。该程序首先对msg进行写入,然后在done管道上发送同步信号,随后从done接收对应的同步信号,最后执行println函数。

若在关闭Channel后继续从中接收数据,接收者就会收到该Channel返回的零值。因此在这个例子中,用close(c)关闭管道代替done <- false依然能保证该程序产生相同的行为。

var done = make(chan bool)
var msg string

func aGoroutine() {
	msg = "你好, 世界"
	close(done)
}

func main() {
	go aGoroutine()
	<-done
	println(msg)
}

对于从无缓冲Channel进行的接收,发生在对该Channel进行的发送完成之前。

基于上面这个规则可知,交换两个Goroutine中的接收和发送操作也是可以的(但是很危险):

var done = make(chan bool)
var msg string

func aGoroutine() {
	msg = "hello, world"
	<-done
}
func main() {
	go aGoroutine()
	done <- true
	println(msg)
}

也可保证打印出“hello, world”。因为main线程中done <- true发送完成前,后台线程<-done接收已经开始,这保证msg = "hello, world"被执行了,所以之后println(msg)的msg已经被赋值过了。简而言之,后台线程首先对msg进行写入,然后从done中接收信号,随后main线程向done发送对应的信号,最后执行println函数完成。但是,若该Channel为带缓冲的(例如,done = make(chan bool, 1)),main线程的done <- true接收操作将不会被后台线程的<-done接收操作阻塞,该程序将无法保证打印出“hello, world”。

对于带缓冲的Channel,对于Channel的第K个接收完成操作发生在第K+C个发送操作完成之前,其中C是Channel的缓存大小。 如果将C设置为0自然就对应无缓存的Channel,也即使第K个接收完成在第K个发送完成之前。因为无缓存的Channel只能同步发1个,也就简化为前面无缓存Channel的规则:对于从无缓冲Channel进行的接收,发生在对该Channel进行的发送完成之前。

我们可以根据控制Channel的缓存大小来控制并发执行的Goroutine的最大数目, 例如:

var limit = make(chan int, 3)
var work = []func(){
	func() { println("1"); time.Sleep(1 * time.Second) },
	func() { println("2"); time.Sleep(1 * time.Second) },
	func() { println("3"); time.Sleep(1 * time.Second) },
	func() { println("4"); time.Sleep(1 * time.Second) },
	func() { println("5"); time.Sleep(1 * time.Second) },
}

func main() {
	for _, w := range work {
		go func(w func()) {
			limit <- 1
			w()
			<-limit
		}(w)
	}
	select{}
}

在循环创建Goroutine过程中,使用了匿名函数并在函数中引用了循环变量w,由于w是引用传递的而非值传递,因此无法保证Goroutine在运行时调用的w与循环创建时的w是同一个值,为了解决这个问题,我们可以利用函数传参的值复制来为每个Goroutine单独复制一份w

循环创建结束后,在main函数中最后一句select{}是一个空的管道选择语句,该语句会导致main线程阻塞,从而避免程序过早退出。还有for{}<-make(chan int)等诸多方法可以达到类似的效果。因为main线程被阻塞了,如果需要程序正常退出的话可以通过调用os.Exit(0)实现。