Go基础

interface

interface变量

interface,就是一组抽象方法的集合,里面到底能存什么值呢?如果我们定义了一个interface的变量,那么这个变量里面可以存实现这个interface的任意类型的对象

我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:

  1. Comma-ok断言 if value, ok := interface.(T); ok

  2. switch测试

switch value := element.(type) {
	case int:
		fmt.Printf("is an int, value is %d\n", index, value)
	case ...
}
// `element.(type)`语法不能在switch外的任何逻辑里面使用

空interface

所有的类型都实现了空interface。有点类似于C语言的void,在我们需要存储任意类型的数值的时候比较有用。

interface函数参数

可通过定义interface参数,让函数接受各种类型的参数。如对于fmt.println

则实现此接口的type即可进行print。实现了error接口的对象(即实现了Error() string的对象),使用fmt输出时,会调用Error()方法。

如何确保某个类型实现了某个接口的所有方法呢?一般可以使用var _Person = (*Student)(nil)进行检测,如果实现不完整,编译期将会报错。

接口不仅能作为函数的参数,还能作为结构体的属性。

面向对象

为什么需要interface? 如若一个类耦合太多的功能,内聚度就不够。不如按功能分出接口,使得多个业务代码不用耦合在一个类里。这是开闭原则的体现。([O]一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。在修改需求的时候,应该尽量通过扩展来实现变化)

依赖倒转原则(D):class 应该依赖接口和抽象类而不是具体的类和函数。

反例

正例

我们在设计一个系统的时候,将模块分为3个层次,抽象层、实现层、业务逻辑层。将抽象层的模块和接口定义出来,这里就需要了interface接口的设计。

Channel

不要通过共享来通信,而要通过通信来共享。

goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。

Select

Go里面提供了一个关键字select,通过select可以监听channel上的数据流动。默认是阻塞的。当多个监听的channel都准备好的时候,随机选择一个处理。

GC

追踪式

程序中运行\所需的数据,一定是栈、数据段这些根节点追踪得到的数据,因此通过可达性分析,三色抽象如下:

  • 白色:init

  • 灰色:直接追踪到的root节点,追踪完成后为黑色

  • 黑色:存活(可达)数据,追踪到的节点标记为灰色

可见,白色的都是垃圾。基于此:

  1. 标记-回收算法:简单,会导致较多的堆内存垃圾;根据BiBOP(Big Bag of Pages)的思想,可以规格化分配内存块、统一管理相同内存大小的块。

  2. 标记-整理算法:移动非垃圾数据,使其紧凑地放在内存中。拷贝开销大。

  3. 复制式回收:分区From To,程序执行时使用From,垃圾回收时复制有用的数据到To,回收From,并交换FromTo空间。降低了堆内存可用量。

  4. 分代回收:基于弱分代假说(参考JVM),数据划分为新生代和老年代,对新生代和老年代执行不同的GC策略。

引用计数:每次对数据对象进行计数,此方案维护开销较大,且循环引用没回收到。

增量式垃圾回收

在三色抽象中,如果不stop-the-world,GC会误判黑色对象对白色对象的新引用(同时无灰色对象引用该白色对象)。提出两个不变式:

  1. 强三色不变式:不允许黑色对象对白色对象的新引用

  2. 弱三色不变式:如果黑色对象对白色对象新引用,也存在灰色对象引用该白色对象。 通过读写屏障维持不变式。 image.png

混合屏障

  • 插入写屏障:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活;

  • 删除写屏障:回收精度低,GC开始时STW扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象。

Go V1.8版本引入了混合写屏障机制(hybrid write barrier),避免了对栈re-scan的过程,极大的减少了STW的时间。结合了两者的优点:

GC时,栈上的新旧对象都标记为黑色,被删除/添加的对象标记为灰色。

多核并行垃圾回收

image.png

Goroutine与协程

大佬的观点:Goroutine是用户态线程,不是协程。

协程:可暂停和恢复执行的procedure(函数)。有这么一些理解:

  1. 本质(核心)是直接调度,也就是可以控制让出和恢复。不是用户态线程(运行的载体)。goroutine是编程语言内部进行调度的,不支持手动控制执行流(yield)。

  2. 协程可以作为IO的载体,以解决IO的不确定性。既然是解决不确定性问题,则可以理解为异步框架,优势是好写。

  3. coroutine可理解为【协函数】,比如C++20的co_wait等待事件。

控制流体操

输出结果

当 panic 被触发时,控制权就被交给了 defer 。后面的代码不执行。

Notes

对等C中void*的数据类型,就是unsafe.Pointer。

最后更新于

这有帮助吗?