深入解析 runtime.Pinner
在 Go 语言的浩瀚宇宙中,每一次版本的更新都如同星辰闪耀,为开发者们带来了无限的可能与惊喜。今天,就让我们一同揭开runtime.Pinner(或相关机制,因为runtime.Pinner并非直接公开的 API)的神秘面纱,探讨它在并发编程中的新角色与区别。
runtime.Pinner:幕后英雄的新篇章
虽然runtime.Pinner这个名字可能并不为大多数开发者所熟知,但它在 Go 运行时的内存管理中扮演着举足轻重的角色。简而言之,runtime.Pinner(或其内部实现)负责将特定的内存页锁定在物理内存中,以防止它们被操作系统交换到磁盘上。这一机制对于需要低延迟、高稳定性的并发应用至关重要。
runtime.Pinner
Pin 固定一个 Go 对象,防止它被垃圾移动或释放收集直到 Pinner.Unpin 方法已被调用。指向固定对象的指针可以直接存储在 C 内存中包含在 Go 内存中,传递给 C 函数。如果是固定对象本身包含指向 Go 对象的指针,这些对象必须单独固定将从 C 代码中访问参数必须是任何类型的指针或[unsafe.Pointer]。在非 go 指针上调用 Pin 是安全的,在这种情况下 Pin 不会做任何事情。
例子 1
Go 侧预先申请好内存,C 侧完成内存赋值
/*
void get_string(void *buf) {
GoSlice* slice = (GoSlice*)(buf);
// 在 C 侧完成内存赋值
memcpy(slice->data, ...);
}
*/
func getStringFromC() {
buf := make([]byte, len)
ptr := unsafe.Pointer(&buf)
sHeader := (*reflect.SliceHeader)(ptr)
// 将 slice 中的 data 指针指向的对象 Pin 住
var pinner runtime.Pinner
defer pinner.Unpin()
pinner.Pin(unsafe.Pointer(sHeader.Data))
// 可以将 slice 对象的指针,安全传给 C 了
C.get_string(ptr)
}
例子 2
类似于 sync.Pool 源码中的 pin,Unpin 操作。
runtime.Pinner
package main
import (
"fmt"
"runtime"
)
func main() {
// 创建一个新的 `[]byte` 数组
data := make([]byte, 10)
// 将数组固定
p := runtime.Pinner.Pin(data)
// 对数组进行一些操作
for i := range data {
data[i] = byte(i)
}
// 取消固定数组
p.Unpin()
// 打印数组内容
fmt.Println(data)
}
sync.Pool 部分源码(go 1.22.5)
func (p *Pool) Get() any {
if race.Enabled {
race.Disable()
}
l, pid := p.pin()
x := l.private
l.private = nil
if x == nil {
// Try to pop the head of the local shard. We prefer
// the head over the tail for temporal locality of
// reuse.
x, _ = l.shared.popHead()
if x == nil {
x = p.getSlow(pid)
}
}
runtime_procUnpin()
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
if x == nil && p.New != nil {
x = p.New()
}
return x
}
对并发编程的深远影响
虽然runtime.Pinner的变化对大多数开发者来说是隐形的,但它们对并发编程的影响却是深远的。这些优化使得 Go 程序在处理高并发、低延迟任务时更加得心应手。开发者可以更加专注于业务逻辑的实现,而不必担心底层内存管理的复杂性。
注意
只能对以下类型的对象调用 Pinner.Pin 函数:
- 通过 new 函数创建的对象
- 复合字面量的地址
- 局部变量的地址
- 如果对非法的对象调用 Pinner.Pin 函数,会导致程序崩溃。
- 在调用 Pinner.Unpin 函数之前,必须确保不再使用该对象,否则会导致程序运行时错误
结语
通过深入优化runtime.Pinner(或其内部机制)等关键组件,Go 语言为开发者们提供了更加强大、稳定的并发编程环境。以上就是 runtime.Pinner。
引用
欢迎关注我的公众号
"彼岸流天"