WeTest腾讯质量开发平台 Go 语言之三驾马车

腾讯WeTest · October 27, 2017 · Last by replied at September 19, 2018 · 1918 hits

作者:唐郑望,腾讯后台开发 工程师
商业转载请联系腾讯 WeTest 获得授权,非商业转载请注明出处。
原文链接:http://wetest.qq.com/lab/view/346.html


WeTest 导读

Go 语言的三个核心设计: interface | goroutine | channel

less is more —— Wikipedia

从 Python 到 Go
远离舒适区
保持饥饿感

一、interface

Go 是一门面向接口编程的语言,interface 的设计自然是重中之重。Go 中对于 interface 设计的巧妙之处就在于空的 interface 可以被当作 “Duck” 类型使用,它使得 Go 这样的静态语言拥有了一定的动态性,却又不损失静态语言在类型安全方面拥有的编译时检查的优势。

(一)source code

从底层实现来看,interface 实际上是一个结构体,包含两个成员。其中一个成员指针指向了包含类型信息的区域,可以理解为虚表指针,而另一个则指向具体数据,也就是该 interface 实际引用的数据。

Itab 的结构如下:

其中 interfacetype 包含了一些关于 interface 本身的信息,_type 表示具体实现类型,在下文 eface 中会有详细描述,bad 是一个状态变量,fun 是一个长度为 1 的指针数组,在 fun[0] 的地址后面依次保存 method 对应的函数指针。go runtime 包里面有一个 hash 表,通过这个 hash 表可以取得 itab,link 跟 inhash 则是为了保存 hash 表中对应的位置并设置标识。主要代码如下:

空接口的实现略有不同。Go 中任何对象都可以表示为 interface{},类似于 C 中的 void*,而且 interface{}中存有类型信息。

Type 的结构如下:

提示:关于 interface 的更多信息,可以参考:https://research.swtch.com/interfaces

(二)i_example

关于 interface 的应用,下面举个简单的例子,是关于 Go 与 Mysql 数据库交互的。

首先在 mysql test 库中创建一张任务信息表:

数据库交互最基本的四个操作:增删改查, 这里以查询为例:

Go 来实现查询这张表里面的所有数据

其中

这段代码可以实现查表这个简单的逻辑,但是有一个小小的问题就是,我们这张表结构比较简单只有 4 个字段,如果换一张有 20+ 个字段甚至更多的表来查询的话,这段代码就显得太过于低效,这个时候我们便可以引入 interface{}来进行优化。

优化后的代码如下:

由于 interface{}可以保存任何类型的数据,所以通过构造 args、values 两个数组,其中 args 的每个值指向 values 相应值的地址,来对数据进行批量的读取及后续操作,值得注意的是 Go 是一门强类型的语言,而且不同的 interface{}是存有不同的类型信息的,在进行赋值等相关操作时需要进行类型转换。

Go 对于 Mysql 事务处理也提供了比较好的支持。一般的操作使用的是 db 对象的方法,事务则是使用 sql.Tx 对象。使用 db 的 Begin 方法可以创建 tx 对象。tx 对象也有数据库交互的 Query,Exec 和 Prepare 方法,与 db 的操作类似。查询或修改的操作完毕之后,需要调用 tx 对象的 Commit() 提交或者 Rollback() 回滚。

例如,现在需要利用事务对之前创建的 user 表进行 update 操作,代码如下

注意: “ := “ 跟 “ = “两个操作符不要弄混淆

如果不需要进行事务处理的话,update 对应的代码如下

可以与上面增加事务操作的代码进行对比,因为操作比较简单所以也就增加了几行代码,以及将 db 对象换成了 tx 对象。

提示:关于 Go 对 sql 的更多支持,可以参考官方文档:https://golang.org/pkg/database/sql/

二、goroutine

并发:同一时间内处理 (dealing with) 不同的事情
并行:同一时间内做 (doing) 不同的事情

Go 从语言层面就支持了并行,而 goroutine 则是 Go 并行设计的核心。本质上,goroutine 就是协程,拥有独立的可以自行管理的调用栈,可以把 goroutine 理解为轻量级的 thread。但是 thread 是操作系统调度的,抢占式的。goroutine 是通过自己的调度器来调度的。

(一)scheduler

Go 的调度器实现了 G-P-M 调度模型,其中有三个重要的结构:M,P,G

M : Machine (OS thread)

P : Context (Go Scheduler)

G : Goroutine

底层的数据结构长这样:


M、P 和 G 之间的交互可以通过下面这几张来自go runtime scheduler的图来展现

上图中看,有 2 个物理线程 M,每一个 M 都拥有一个上下文 P,也都有一个正在运行的 goroutine G。图中灰色的那些 G 并没有运行,而是出于 ready 的就绪态,正在等待被调度。由 P 来维护着这个 runqueue 队列。

图中的 M1 可能是被新建出来的,也可能是从线程缓存中取出来的。当 M0 返回时,它必须尝试获取 P 来运行 G,通常情况下,它会尝试从其他的 thread 那里” steal” 一个 P 过来,失败的话,它就把 G 放在一个 global runqueue 里,然后自己会被放入线程缓存里。所有的 P 会周期性的检查 global runqueue,否则 global runqueue 上的 G 永远无法执行。

另一种情况是 P 所分配的任务 G 很快就执行完了(因为分配不均),这就导致了某些 P 处于空闲状态而系统却依然在运行态。但如果 global runqueue 没有任务 G 了,那么 P 就不得不从其他的 P 那里拿一些 G 来执行。通常情况下,如果 P 从其他的 P 那里要偷一个任务的话,一般就 ‘steal’ runqueue 的一半,这就确保了每个 thread 都能充分的使用。

P 如何从其他 P 维护的队列中” steal” 到 G 呢?这就涉及到 work-stealing 算法,关于该算法的更多信息可以参考:https://rakyll.org/scheduler/

(二)g_example

举个简单的例子来演示下 goroutine 是如何运行的

这段代码非常简单,两个不同的 goroutine 异步运行
运行结果如下:

然后做个小小的改动,只是将 main() 中的两个函数的位置互换,其余代码变:

会出现一件有意思的事情:

原因也很简单,因为 main() 返回时, 并不会等待其他 goroutine(非主 goroutine) 结束。对上面的例子, 主函数执行完第一个 say() 后,创建了一个新的 goroutine 没来得及执行程序就结束了,所以会出现上面的运行结果。

三、channel

goroutine 在相同的地址空间中运行,因此必须同步对共享内存的访问。Go 语言提供了一个很好的通信机制 channel,来满足 goroutine 之间数据的通信。channel 与 Unix shell 中的双向管道有些类似:可以通过它发送或者接收值。

source code

其中 waitq 的结构如下

可以看到 channel 其实就是一个队列加一个锁。其中 sendx 和 recvx 可以看做生产者跟消费者队列,分别保存的是等待在 channel 上进行读操作的 goroutine 和等待在 channel 上进行写操作的 goroutine,如下图所示。

写 channel (ch <- x) 的具体实现如下 (只选取了核心代码):
具体可以分为三种情况:

— 有 goroutine 阻塞在 channel 上,而且 chanbuf 为空,直接将数据发送给该 goroutine 上。

— chanbuf 有空间可用:将数据放到 chanbuf 里面。

— chanbuf 没有空间可用:阻塞当前 goroutine。

读 channel( <-ch)和发送的操作类似,就不帖代码展示了。

c_example

关于 goroutine 跟 channel 进行通信的一个简单的例子,逻辑很简单:

这里我们定义了两个带缓存的 channel jobs 和 results,如果把这两个 channel 都换成不带缓存的,就会报错,不过可以这样进行处理就可以了:

比较常见的 channel 操作还有 select , 存在多个 channel 的时候,可以通过 select 可以监听 channel 上的数据流动。

因为 ch1 和 ch2 都为空,所以 case1 和 case2 都不会读取成功。 则 select 执行 default 语句。

这篇文章是对这段时间学习 Go 的一次小结,也算是抛砖引玉,文中如有理解不对或者描述错误的地方,也恳请大家批评指正,关于 Go 的学习,更希望能与大家多多交流,谢谢!


关于腾讯 WeTest (wetest.qq.com)

腾讯 WeTest 是腾讯游戏官方推出的一站式游戏测试平台,用十年腾讯游戏测试经验帮助广大开发者对游戏开发全生命周期进行质量保障。腾讯 WeTest 提供:适配兼容测试;云端真机调试;安全测试;耗电量测试;服务器性能测试;舆情分析等服务。

点击地址:http://wetest.qq.com/立即体验!

共收到 2 条回复 时间 点赞

wetest 什么地方用到 go 了

写得挺清楚的

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up