【学习笔记】golang基础3

61 阅读5分钟

golang基础3

1.channel

管道是动态的,通信模型,优于共享内存(可能存在不安全)。

结构为缓存区、发送队列,接受队列。

缓存区为环形内存模型。

添数据和取数据会被上锁。

其他的其实好像也没啥好说的。

2.网络编程

补充一下计网知识。

这样也是教科书式基本的分层模型,但是会有更生动的现代应用例子对应。应用层实际上是开发者使用的一些协议,而传输层是更基础的通信协议,如果需要更高的性能,则需要利用从tcp工具开始编写。

关于socket

由于三次握手四次挥手是统一,操作系统提供高度抽象的socket。

关于IO模型

IO模型是操作socket的方案。

阻塞IO:落后,用户态和内核态频繁切换。

非阻塞IO:轮询socket

多路复用:使用事件池,epoll(事件源发生变化才发出通知,而不是轮询,节省资源)

关于epoll

epoll的三个主要操作

  1. 创建epoll实例(epoll_create)
    • 就像建立一个监控系统。我们告诉系统我们要开始监控多个事件源。
  2. 注册事件(epoll_ctl)
    • 这个步骤可以比作在监控系统中添加摄像头。每个摄像头(事件源)都被配置为关注某些特定的动作(如“是否有人走过”)。我们通过epoll_ctl将这些事件源添加到epoll实例中,并告诉epoll我们关心什么类型的事件(如“数据到来”)。
  3. 等待事件触发(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的分级表我拷贝过来:

classbytes/objbytes/spanobjectstail wastemax waste
1881921024087.50%
2168192512043.75%
3248192341029.24%
4328192256046.88%
54881921703231.52%
6648192128023.44%
78081921023219.07%
6732768327681012.50%

微对象分配:从mcache拿到二级span,将多个微对象合成一个16字节存入。

大对象分配:直接在heapArena中开辟分配0级定制span。

怎么找垃圾

GCroot开始串行DFS,可达性分析,没有被引用就是垃圾0。不过串行效率不高。

go使用并发。并发方法关键在于标记,需要插入屏障和删除屏障。

三色标记法:黑、灰、白。白色为要清除,黑色为已遍历,灰为当前遍历。

插入屏障:新加入的节点标记为灰色。

删除屏障:被删除节点标记为灰色。

gc触发条件

系统定时触发或申请内存触发

优化原则

避免堆上产生垃圾:

1.内存池化:在堆上做一个内存池,作为内存的供应商。避免反复在堆上开销从而产生碎片。

2.减少逃逸,fmt少用,因为有反射,多用log

3.使用空结构体:空结构体是一个固定地址,不占用堆。

gc调优工具

juejin.cn/post/690428…

内存分配参考文献

draveness.me/golang/docs…

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)
}