导语:Go语言的三个核心设计: interface 、goroutine 、 channelmysql
less is more —— Wikipedia算法
Go是一门面向接口编程的语言,interface的设计天然是重中之重。Go中对于interface设计的巧妙之处就在于空的interface能够被看成“Duck”类型使用,它使得Go这样的静态语言拥有了必定的动态性,却又不损失静态语言在类型安全方面拥有的编译时检查的优点。sql
从底层实现来看,interface其实是一个结构体,包含两个成员。其中一个成员指针指向了包含类型信息的区域,能够理解为虚表指针,而另外一个则指向具体数据,也就是该interface实际引用的数据。shell
其中 interfacetype 包含了一些关于interface自己的信息,_type表示具体实现类型,在下文eface中会有详细描述,bad 是一个状态变量,fun是一个长度为1的指针数组,在 fun[0] 的地址后面依次保存method对应的函数指针。go runtime 包里面有一个hash表,经过这个hash表能够取得 itab,link跟inhash则是为了保存hash表中对应的位置并设置标识。主要代码以下:数据库
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 的应用,下面举个简单的例子,是关于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对象。
并发:同一时间内处理(dealing with)不一样的事情
并行:同一时间内作(doing)不一样的事情
Go从语言层面就支持了并行,而goroutine则是Go并行设计的核心。本质上,goroutine就是协程,拥有独立的能够自行管理的调用栈,能够把goroutine理解为轻量级的thread。可是thread是操做系统调度的,抢占式的。goroutine是经过本身的调度器来调度的。
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算法,关于该算法的更多信息能够参考这篇文章。
举个简单的例子来演示下goroutine是如何运行的
这段代码很是简单,两个不一样的goroutine异步运行
运行结果以下:
而后作个小小的改动,只是将main()中的两个函数的位置互换,其他代码变:
会出现一件有意思的事情:
缘由也很简单,由于main()返回时, 并不会等待其余goroutine(非主goroutine)结束。对上面的例子, 主函数执行完第一个say()后,建立了一个新的goroutine没来得及执行程序就结束了,因此会出现上面的运行结果。
goroutine在相同的地址空间中运行,所以必须同步对共享内存的访问。Go语言提供了一个很好的通讯机制channel,来知足goroutine之间数据的通讯。channel与Unix shell 中的双向管道有些相似:能够经过它发送或者接收值。
其中waitq的结构以下
能够看到channel其实就是一个队列加一个锁。其中sendx和recvx能够看作生产者跟消费者队列,分别保存的是等待在channel上进行读操做的goroutine和等待在channel上进行写操做的goroutine,以下图所示。
写channel (ch <- x)的具体实现以下(只选取了核心代码):
具体能够分为三种状况:
有goroutine阻塞在channel上,并且chanbuf为空,直接将数据发送给该goroutine上。
chanbuf有空间可用:将数据放到chanbuf里面。
chanbuf没有空间可用:阻塞当前goroutine。
读channel( <-ch)和发送的操做相似,就不帖代码展现了。
关于goroutine跟channel进行通讯的一个简单的例子,逻辑很简单:
这里咱们定义了两个带缓存的channel jobs 和 results,若是把这两个channel都换成不带缓存的,就会报错,不过能够这样进行处理就能够了:
比较常见的channel操做还有select , 存在多个channel的时候,能够经过select能够监听channel上的数据流动。
由于 ch1 和 ch2 都为空,因此 case1 和 case2 都不会读取成功。 则 select 执行 default 语句。