当开发需要依赖底层平台或处理器体系特性的Go包时,提供对应的特定实现是非常有必要的。
Go没有预处理,没有宏定义系统,不可以像c语言那样使用#define
来控制是否包含平台相关的特定代码。作为替代,Go使用go/build包中定义的标签系统(system of tags
)和命名约定(naming convention
)以及go tool
中的相应支持来允许Go包编译特定代码。
这篇文章说明条件编译是如何实现的以及如何在你自己的工程中使用它。
pprof.go
中的init初始化函数,使用http.Handle
注册了一系列页面接口。
当在应用层的代码import _ "net/http/pprof"
时,会执行init函数。即完整一些默认页面接口的注册。
如果你的程序没有开启http服务,那么还需要添加如下代码开启http服务:
我们在使用Go语言时,经常涉及到[]byte和string两种类型间的转换。本篇文章将讨论转换时的开销,Go编译器在一些特定场景下对转换做的优化,以及在高性能场景下,我们自己如何做相应的优化。
[]byte其实就是byte类型的切片,对应的底层结构体定义如下(在runtime/slice.go
文件中)
底层双向链表,首尾相连,形成环。
只有一个结构体,数据成员next和prev私有不对外暴露,Value为interface{}类型,外部可以直接访问。
可通过Next和Prev访问前后的元素。
注意,Next调用时如果next为nil,那么会将当前Ring元素的next和prev设置为自己。其实我觉得直接panic也可以。因为这属于未初始化的Ring。是错误用法。
func (r *Ring) Link(s *Ring) *Ring
连接两个ring。假设链接前:
是双向链表。
实现上,有哨兵root
节点,即当链表为空时,还有一个哨兵元素。
实现多态的方式是Element
结构体内有一个interface{}
数据成员,可以用来存储任意类型的数据。其余链表和链表元素的代码都是系统库提供的。
container/list/example_test.go
演示了如何申请一个List,并对它进行一些插入操作后,如何遍历访问该List。
有一点非常值得思考,就是Go语言没有构造函数,从而导致的一些问题。
当一个结构体变量在使用前需要对它的数据成员做一些非zero out的初始化时(Go语言会对所有数据成员做zero out),
要么是提供一个New接口,接口返回一个结构体的指针类型,内部实现是实例化结构体变量后,然后做Init操作。但是从语意上来讲,这种变量是分配在堆上的。
要么是提供一个Init接口,坏处是要么用户在栈上声明了结构体变量时,还需要手动调用Init函数,增加了使用时成本。要么是在其他的方法内部做lazy init,这又增加了结构体的实现复杂度,以及小小的性能(毕竟lazy init每次都要判断是否已经init了)。
如何清空链表?
list并没有提供类似clear的方法,所以必须循环遍历删除。
这里有个坑,就是遍历时调用Remove删除自身Element时,自身Element的Next将被修改为nil,所以需要在调用Remove之前,把Next记录下来,才能继续访问下一个元素。
额外的开销
由于list是通过在应用层提供的item之外包装一层Element对象的方式实现的,所以即使你是同一个item不停的插入和删除list,依然会不停的new Element和删除Element,这点还是挺糟糕的。
List
提供的对外接口有:
/src/container/heap
除了支持建堆(Init)、插入(Push)、删除堆顶元素(Pop)的常规操作外,还支持删除指定位置元素(Remove)、指定位置元素的值发生变化后堆重新排序的操作(Fix)。
实现手法是提供了一系列自身无状态的函数(后续称这些函数为系统库函数
),系统库函数内部实现堆的算法,并在需要操作容器的时候(也就是在算法中的合适位置)调用用户实现了heap.Interface
接口的结构体对象(或者叫容器)的方法。
算法部分采用二叉堆。
由于系统库函数并不直接访问用户层的数据(只能通过用户类型实现heap.Interface
的方法来操作用户的数据),从某种角度来说,用户甚至可以使用链表而非数组作为容器。当然,那样的话用户层所有通过下标访问数据的操作就变复杂了,复杂度也增加了。不会真有人这么做。这里只为说明Go这种抽象手法的灵活性。
/src/container/heap/example_intheap_test.go
中演示了如何实现一个存放基础类型int数组切片的最小堆。
如果是最大堆,只需将接口Less
中的实现修改为大于即可。
/src/container/heap/example_pq_test.go
中演示了如何实现自定义结构体类型堆,另一方面实现了优先级队列(PriorityQueue
),它比上面intheap
例子中多了一个操作,可对堆中元素做修改,然后让堆重新调整保证堆特性。
基于Go 1.11.4
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/40957/
Go语言的运行时管理了调度,垃圾回收,和协程的运行时环境。这里我们只讨论调度器。
运行时调度器将协程映射到系统线程上执行。协程是轻量级的线程,启动协程的开销十分小。每个协程被一个称为G的结构体所描述,它包含了记录协程栈和当前状态的必要字段。所以,G = 协程。
运行时保持跟踪每个G并将它们映射到逻辑处理器上,逻辑处理器称为P。P可以被看成一个抽象资源或者一个上下文(context),它需要被系统线程(称为M,或者Machine)获取,然后系统线程才可以执行G。
你可以通过在运行时调用runtime.GOMAXPROCS(numLogicalProcessors)
来控制逻辑处理器的数量,如果你真打算调整这个参数(也许你不应该调整它),最好只设置一次,因为这个调用会引起垃圾回收器STW(即程序暂停执行)。
基本来说,操作系统运行线程,线程运行你的代码。Go的把戏是编译器在运行时的一些位置插入一些代码(比如通过channel发送数据,调用运行时包中的函数等),这样Go才能通知调度器做相应的处理。
注:上图来源于Analysis of the Go runtime scheduler
本篇文章首先简单介绍了TCP keepalive的机制以及运用场景。接着介绍了Go语言中如何开启与设置TCP keepalive。但是由于Go语言最上层的接口不够灵活,从而引出在Go语言中如何使用系统调用设置TCP连接的文件描述符属性。接着原作者就掉坑里了。。。最后介绍了在
Go 1.11
之后的版本如何使用新的接口设置TCP连接的文件描述符属性。
为了更适合中文阅读,我对文章做了些增删,并没有逐字翻译。原文地址:Notes on TCP keepalive in Go | TheNotExpert。