概述
有時候我們需要在完全可控的范圍內(nèi)復用channel,但是關閉了的channel原生語法并沒有提供方法打開,所以利用指針再次打開。
channel的結(jié)構(gòu)體在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直接修改它的值。
代碼實現(xiàn)
//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所在數(shù)據(jù)空間,這里的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內(nèi)存優(yōu)化(一)— struct對齊
在上面主要用了指針定位closed值,直接修改標志位。為了保證設置closed值的安全性所以在給它設置值的時候使用runtime.lock上鎖。
關于go:linkname可以自行百度,在目錄下創(chuàng)建一個*.s文件可以躲過編譯時error:missing function body。