Go小技巧(二)— 打开已经关闭的channel

2018-07-02  本文已影响221人  sipt

概述

有时候我们需要在完全可控的范围内复用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

上一篇下一篇

猜你喜欢

热点阅读