golang基础3
1.channel
管道是动态的,通信模型,优于共享内存(可能存在不安全)。
结构为缓存区、发送队列,接受队列。
缓存区为环形内存模型。
添数据和取数据会被上锁。
其他的其实好像也没啥好说的。
2.网络编程
补充一下计网知识。
这样也是教科书式基本的分层模型,但是会有更生动的现代应用例子对应。应用层实际上是开发者使用的一些协议,而传输层是更基础的通信协议,如果需要更高的性能,则需要利用从tcp工具开始编写。
关于socket
由于三次握手四次挥手是统一,操作系统提供高度抽象的socket。
关于IO模型
IO模型是操作socket的方案。
阻塞IO:落后,用户态和内核态频繁切换。
非阻塞IO:轮询socket
多路复用:使用事件池,epoll(事件源发生变化才发出通知,而不是轮询,节省资源)
关于epoll
epoll的三个主要操作:
- 创建epoll实例(epoll_create):
- 就像建立一个监控系统。我们告诉系统我们要开始监控多个事件源。
- 注册事件(epoll_ctl):
- 这个步骤可以比作在监控系统中添加摄像头。每个摄像头(事件源)都被配置为关注某些特定的动作(如“是否有人走过”)。我们通过
epoll_ctl将这些事件源添加到epoll实例中,并告诉epoll我们关心什么类型的事件(如“数据到来”)。
- 这个步骤可以比作在监控系统中添加摄像头。每个摄像头(事件源)都被配置为关注某些特定的动作(如“是否有人走过”)。我们通过
- 等待事件触发(epoll_wait):
- 这个就像门卫在监控屏幕前等待。只有当某个摄像头(事件源)检测到有人经过(事件触发)时,门卫才会收到通知并采取行动。
epoll_wait就是在等待这些事件的触发。
- 这个就像门卫在监控屏幕前等待。只有当某个摄像头(事件源)检测到有人经过(事件触发)时,门卫才会收到通知并采取行动。
关于go对socket的抽象
goroutine-per-connection是开发者做的事情。我们给每一个连接一个协程。而go用net包封装出来网络编程的工具。
3.GC
继续围绕go的高并发特点。
连续栈:空间不足,变为原来两倍。使用率不足25%,变为原来一半。优点是空间连续,缺点是开销大。
逃逸:栈逃到堆上。指针逃逸、空接口逃逸,大变量逃逸。
指针逃逸主要是因为返回了指针对象。栈是会被回收的,一不小心就会变成空指针。
空接口逃逸主要是因为它会使用反射去查看类型。
大变量逃逸主要是怕栈空间不足,一般64位机器,大于64KB就会逃逸。
关于heapArena
翻译过来叫堆竞技场。
mspan:内存管理单元
其中,mcentral是mspan的索引集。scan代表gc是否扫描。
线程缓存就是每一个线程对应着自己独有的一块内存。避免多个线程之间产生内存的竞争冲突。
mcache中mspan填满后,与mcentral交换新的mspan。
mcentral不足,在heapArena开辟新mspan。
对象分级
Tiny 微对象 (0,16B) 无指针
Small 小对象 [16B,32KB]
Large 大对象 (32KB,+∞)
对于span的分级表我拷贝过来:
| class | bytes/obj | bytes/span | objects | tail waste | max waste |
|---|---|---|---|---|---|
| 1 | 8 | 8192 | 1024 | 0 | 87.50% |
| 2 | 16 | 8192 | 512 | 0 | 43.75% |
| 3 | 24 | 8192 | 341 | 0 | 29.24% |
| 4 | 32 | 8192 | 256 | 0 | 46.88% |
| 5 | 48 | 8192 | 170 | 32 | 31.52% |
| 6 | 64 | 8192 | 128 | 0 | 23.44% |
| 7 | 80 | 8192 | 102 | 32 | 19.07% |
| … | … | … | … | … | … |
| 67 | 32768 | 32768 | 1 | 0 | 12.50% |
微对象分配:从mcache拿到二级span,将多个微对象合成一个16字节存入。
大对象分配:直接在heapArena中开辟分配0级定制span。
怎么找垃圾
GCroot开始串行DFS,可达性分析,没有被引用就是垃圾0。不过串行效率不高。
go使用并发。并发方法关键在于标记,需要插入屏障和删除屏障。
三色标记法:黑、灰、白。白色为要清除,黑色为已遍历,灰为当前遍历。
插入屏障:新加入的节点标记为灰色。
删除屏障:被删除节点标记为灰色。
gc触发条件
系统定时触发或申请内存触发
优化原则
避免堆上产生垃圾:
1.内存池化:在堆上做一个内存池,作为内存的供应商。避免反复在堆上开销从而产生碎片。
2.减少逃逸,fmt少用,因为有反射,多用log
3.使用空结构体:空结构体是一个固定地址,不占用堆。
gc调优工具
内存分配参考文献
4.高级特性
关于cgo
可以在go里面嵌入C代码。好处基本就是可以用现成c代码,性能是不太ok,毕竟是go去调用C而不是直接用C。
recover()
panic+defer+recover可以回到上一级协程完成defer。
defer的话可以理解成一个栈(先进后出),在当前协程(可以叫当前方法)return的时候清空栈匣(全部弹出)。
reflect
让程序在运行时动态地检查和操作变量的类型和值。
有一段代码示例,我们可以通过CallAdd调用不同的Add方法,但这里只写了一个:
package main
import (
"fmt"
"reflect"
)
func MyAdd(a, b int) int { return a + b }
func CallAdd(f func(a int, b int) int) {
v := reflect.ValueOf(f)
if v.Kind() != reflect.Func {
return
}
argv := make([]reflect.Value, 2)
argv[0] = reflect.ValueOf(1)
argv[1] = reflect.ValueOf(1)
result := v.Call(argv)
fmt.Println(result[0].Int())
}
func main() {
CallAdd(MyAdd)
}