美文网首页
Go小技巧(二)— 打开已经关闭的channel

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

作者: sipt | 来源:发表于2018-07-02 15:40 被阅读221次

    概述

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

    相关文章

      网友评论

          本文标题:Go小技巧(二)— 打开已经关闭的channel

          本文链接:https://www.haomeiwen.com/subject/zkdluftx.html