深入浅出 Go-Map(上)

1.24 前后 map 的世界,在 1.24 后,go 的 map 换了实现方式,让我们来康康 1 追踪 map 的起始点 看下面这段代码,我们来定位一下 make 之后发生了什么? package main func main() { _ = make(map[string]int, 16) } 使用 go tool compile -S 编译文件,可以发现 map 实际上是定位到了底层的 runtime.makemap 中,这使得我们有了窥探 map 源码的机会。 ❯ go tool compile -S main.go | grep 'make' 0x0034 00052 (.../main.go:4) CALL runtime.makemap(SB) rel 52+4 t=R_CALLARM64 runtime.makemap+0 在进入具体方法之前,我们先了解一下 1.24 之前 go 的 map 的结构 2 1.24 前的世界 本节 go 的代码是基于 1.23.2 版本的 2.1 map 的基本构造 在 1.24 前,go 的 map 实现是基于桶数组 + 溢出桶实现的,具体而言是基于 runtime 下的这两个结构体实现的。 首先是 hmap,他是宏观上的 map,包含了 map 的键值对数量、状态、桶的数量以及桶的指针等等。当我们创建了一个 map 的时候,宏观来看就是创建 hmap。 ...

October 29, 2025 · 小石堆

深入浅出 Go-GC

&&& Golang GC 流程 Go 如何启动 GC Go 触发 GC 有三种情况: 主动调用 runtime.gc() 可能会触发 Gc 当分配对象时可能会触发 Gc 守护协程定时 Gc runtime/mgc.go const ( // 根据堆分配内存情况,判断是否触发 GC gcTriggerHeap gcTriggerKind = iota // 定时触发 GC gcTriggerTime // 手动触发 GC gcTriggerCycle } func (t gcTrigger) test() bool { // ... switch t.kind { case gcTriggerHeap: // ... // 如果是堆内存分配导致的 GC,会 Check 当前堆内存的使用情况 trigger, _ := gcController.trigger() return atomic.Load64(&gcController.heapLive) >= trigger case gcTriggerTime: // 如果是守护协程 GC,则会check当前距离上一次 GC 是否已经达到 2 min if gcController.gcPercent.Load() < 0 { return false } lastgc := int64(atomic.Load64(&memstats.last_gc_nanotime)) return lastgc != 0 && t.now-lastgc > forcegcperiod case gcTriggerCycle: // ... // 如果是手动触发 GC,check 前一轮 GC 有没有结束 return int32(t.n-work.cycles) > 0 } return true } Gc - Start Golang 的标记清扫算法的关键点就是标记这一步,其采用三色标记法,从 Root 对象开始进行可达性分析,关键步骤如下: ...

September 19, 2025 · 小石堆

大模型调用的流式输出解析

从流式输出到服务端推送技术再到 Java 的 WebFlux 大模型流式输出 像 ChatGPT 这样的网页,我们不难发现问出问题后,大模型吐字是一段接一段的,但我们传统的 Http 请求,一般是每次获取一段数据就要再次发起请求一次。这是一种耗费资源的方式,简言之就是 Http 轮询(短轮询和长轮询 Comet),所以服务端主动推送数据的计数就应运而生。 服务端主动推送技术 这里抛开 Http 轮询计数,主要涉及到了 SSE 和 WebSocket,其实 SSE 和 WebSocket 都是服务于 “实时” 二字的。 SSE 协议 SSE(Server Send Events),顾名思义,服务端发送事件,是指服务端能够主动给客户端发送消息。其基于 Http 协议,需要按照 SSE 协议规范在消息响应体中填充数据,如果需要 SSE 协议,则需要 Http 长连接(默认),并且将请求中的 content-type 设置为 text/event-stream。 其原理实际上是在建立好的 Http 连接上,于客户端协商,返回的类型不为一次性的数据包,而是返回一个 Stream。而基于这个 Strem,服务器可以不断的往内部填入数据,客户端也可以依次接受数据。 其实 SSE 是比较常见的,因为很多时候,只需要服务器推送给客户端,而客户端不需要给服务器发送内容,比如说在一个常用开源容器监控系统 Dozzle 中,就能看到其身影。可以类比,如果一个系统,类似比赛的看板或者日志的看板,就比较适合用 SSE 协议。 因为 SSE 并非一个完全新的协议,而是使用了 Http 协议的功能,并定义一系列规范,所以 SSE 的优点就是: 轻量级(并非全新协议)(相较于 WebSocket) 基于 Http,基本上所有的浏览器都支持、 支持断开重连 缺点: ...

September 14, 2025 · 小石堆

Java ArrayList & Go Slice 扩容

Java 中的 ArrayList 类似 Golang 里的 Slice,都是动态数组 ArrayList 什么是 ArrayList ArrayList 是 Java 中最常见的动态数组之一,他实现了 List 接口,底层基于数组实现,但能自动扩容,可以理解为变长数组的一种。 List<String> list = new ArrayList<>(); list.add("zhangsan"); list.add("lisi"); System.out.println(list); ArrayList 的扩容原理 当创建一个 ArrayList 的时候,默认初始的容量为 10,其内部实现类似 Object[] elementData = new Object[10] 当元素超过容量时,arraylist 就会触发扩容机制,其触发条件为 if(size == elementData.length) 扩容策略如下 newCapacity = oldCapacity + (oldCapacity + 1),简言之就是扩容为原来的 1.5 倍。 这里涉及到扩容过程的主要函数是 grow() private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity + 1); if(newCapacity - minCapacity < 0) { newCapacity = minCapacity; } // 创建新数组,复制旧数据 elementData = Arrays.copyOf(elementData, newCapacity) } 这里的扩容规则实际上并没有 Go 的复杂,但是要理解 minCapacity ...

September 3, 2025 · 小石堆

浅谈 IO 多路复用

之前面试准备过这里的八股,但觉得理解不够深,故打算再探深些 五种 IO 模型 阻塞 IO 非阻塞 IO IO 多路复用 信号驱动 IO 异步 IO 我们先来看看 DMA 存在的必要性 当数据到达设备端,比如说网卡。网卡接收到数据后,传统非 DMA 的形式,此时会直接触发中断,此时 CPU 介入,负责解析数据,并且将数据 Copy 到 CPU 寄存器中,然后再将 CPU 寄存器中的数据 Copy 到内核内存中,然后解析再送到 Socket 缓冲区中,最后唤醒被阻塞的线程。分析上述流程,CPU 作为整个计算机的大脑,让他来处理这种傻白甜的反复读写操作,过于浪费,这就是 DMA 存在的必要性。当网卡收到数据后,通知 DMA,DMA 负责直接将数据 Copy 到内核内存中,然后网卡触发中断,CPU 将数据进行网络协议栈的解析,然后 Copy 到对应 Socket 的缓冲区中,然后唤醒线程。等到线程被调度的时候,会进入内核态,发现数据已经准备好,然后 Copy 到内存中直接使用,这就是现代计算机的一次阻塞 IO 过程。 阻塞 IO 最简单,最易懂 厨子做菜,做不好服务生也等着,等到菜做好再上菜。 上面其实已经讲过一次阻塞 IO 的流程了,只不过是站在 DMA 和 CPU 的角度来看的。我们来看看阻塞 IO 的瓶颈有哪些? 线程在 Read 数据的时候,如果数据没有准备好,那就陷入阻塞。假设数据不是必要的,有其他重要的事情做,那这里就会成为瓶颈,线程被阻塞,无法做事。 在阻塞 IO 的模型,一个 Scoket 对应一个线程,如果此时有上万个网络连接,那内核线程能否支撑这样大的并发量,所以这里也会成为瓶颈。 所以,阻塞 IO 虽然实现起来傻白甜,那是内核要考虑的问题就多了。 非阻塞 IO 厨子做菜,服务生不断轮询反问厨子有没有做好。 这一点对应到 CPU 就是进程不断调用 read(),如果数据准备好了,read()出数据,如果数据没有准备好,read()出 fasle,得到 false 的进程就去继续调用 read()。 ...

August 11, 2025 · 小石堆