概述
有时候我们需要在完全可控的范围内复用channel,但是关闭了的channel原生语法并没有提供方法打开,所以利用指针再次打开。
channel的结构体在chan.go
中:
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
//... 以下字段没有用上,先省略
}
Channel是否关闭取决于hchan.closed
,0是打开,1是关闭。
方法:让指针指向hchan.closed
直接修改它的值。
代码实现
//go:linkname lock runtime.lock
func lock(l *mutex)
//go:linkname unlock runtime.unlock
func unlock(l *mutex)
func open(c interface{}) error {
v := reflect.ValueOf(c)
if v.Type().Kind() != reflect.Chan {
return errors.New("type must be channel")
}
i := (*[2]uintptr)(unsafe.Pointer(&c)) //定位c所在数据空间,这里的c是个指针所以要进行一步取值
var closedOffset, lockOffset uintptr = 28, 88
closed := (*uint32)(unsafe.Pointer(i[1] + closedOffset)) //指向closed的地址
if *closed == 1 {
lockPtr := (*mutex)(unsafe.Pointer(i[1] + lockOffset)) //指向lock地址
lock(lockPtr) //上锁
if *closed == 1 {
*closed = 0 //直接修改值
}
unlock(lockPtr) //解锁
}
return nil
}
closedOffset为什么是28呢?这个涉及到struct对齐问题,Go内存优化(一)— struct对齐
在上面主要用了指针定位closed值,直接修改标志位。为了保证设置closed值的安全性所以在给它设置值的时候使用runtime.lock上锁。
关于go:linkname
可以自行百度,在目录下创建一个*.s
文件可以躲过编译时error:missing function body
。