io之pipe

io包中通过pipe实现了管道

源码

// onceError is an object that will only store an error once.
// 加锁的错误信息
// 保证并发读写的安全
type onceError struct {
    sync.Mutex // guards following
    err        error
}

// 加锁防止并发读写
func (a *onceError) Store(err error) {
    a.Lock()
    defer a.Unlock()
    if a.err != nil {
        return
    }
    a.err = err
}

func (a *onceError) Load() error {
    a.Lock()
    defer a.Unlock()
    return a.err
}

// ErrClosedPipe is the error used for read or write operations on a closed pipe.
var ErrClosedPipe = errors.New("io: read/write on closed pipe")

// A pipe is the shared pipe structure underlying PipeReader and PipeWriter.
// pipe管道结构,注意是小写,外部不可见
type pipe struct {
    // 锁
    wrMu sync.Mutex // Serializes Write operations
    // 通道channel,无缓存
    wrCh chan []byte
    // 记录最近一次读取出的字节数,也是无缓存channel
    rdCh chan int

    // 保证close(done)只会执行一次,多次会panic
    // sync.Once之前源码解析过
    once sync.Once // Protects closing done
    // 用来标记pipe是否关闭
    done chan struct{}
    // 记录读写错误信息
    rerr onceError
    werr onceError
}

// 从pipe读取数据到b
func (p *pipe) Read(b []byte) (n int, err error) {
    // 检查一次pipe是否关闭
    select {
    case <-p.done:
        return 0, p.readCloseError()
    default:
    }

    // 要么从wrCh读取出数据
    // 要么done被close,否则会阻塞等待
    select {
    // 从channel中读取数据
    case bw := <-p.wrCh:
        // rdCh记录实际读取的字节数
        // 因为b可能比bw小,所以read并不一定会把bw全部读出
        nr := copy(b, bw)
        p.rdCh <- nr
        return nr, nil
    // 判断pipe是否关闭
    case <-p.done:
        return 0, p.readCloseError()
    }
}

// 返回一个读取已关闭pipe的错误
func (p *pipe) readCloseError() error {
    rerr := p.rerr.Load()
    if werr := p.werr.Load(); rerr == nil && werr != nil {
        return werr
    }
    return ErrClosedPipe
}

// 读端主动关闭pipe
func (p *pipe) CloseRead(err error) error {
    if err == nil {
        err = ErrClosedPipe
    }
    p.rerr.Store(err)
    p.once.Do(func() { close(p.done) })
    return nil
}

// 将b的数据写入pipe
func (p *pipe) Write(b []byte) (n int, err error) {
    // 同样先检查pipe是否关闭
    select {
    case <-p.done:
        return 0, p.writeCloseError()
    default:
        // 注意如果pipe未关闭,继续执行后面之前需要加锁,至于为什么,往下看
        p.wrMu.Lock()
        defer p.wrMu.Unlock()
    }

    // 不管b是不是空的,至少保证执行一次,原因就是解除正在等待的reader的阻塞状态
    // 第一次运行之后,后面就判断b是否已经全部通过pipe写入
    for once := true; once || len(b) > 0; once = false {
        select {
        // 将b写入到wrCh中
        // 因为wrCh没有缓存
        // 如果没有reader在等待读,就跳过这个case
        // 如果有reader在等待读,就将p直接传递给reader(具体实现可以看之前的channel源码解析)
        case p.wrCh <- b:
            // 这里rdCh发挥作用了
            // 到这一步,reader已经读取完了
            // 通过获取reader实际读取到的字来判断p是否被读取完了
            // 如果没有读取完,还会继续往pipe中写,直到下次reader继续读取
            // 这里也能解答为何上面会上锁,因为p可能分两次写pipe,但是对于写端是黑盒的,写端认为是一次原子写入
            nw := <-p.rdCh
            // b有可能没有读完
            b = b[nw:]
            n += nw
        // 如果pipe关闭了,就返回实际写入到字节数和错误信息
        case <-p.done:
            return n, p.writeCloseError()
        }
    }
    return n, nil
}

func (p *pipe) writeCloseError() error {
    werr := p.werr.Load()
    if rerr := p.rerr.Load(); werr == nil && rerr != nil {
        return rerr
    }
    return ErrClosedPipe
}

// 写端主动关闭pipe
// 除了错误信息不一样,其他动作跟读端主动关闭pipe是一致的
func (p *pipe) CloseWrite(err error) error {
    if err == nil {
        err = EOF
    }
    p.werr.Store(err)
    p.once.Do(func() { close(p.done) })
    return nil
}

// A PipeReader is the read half of a pipe.
// 后面分别使用PipeReader和PipeWriter来包装pipe的读写能力
// 即读端和写端,读端只提供读的能力,写端只提供写的能力
type PipeReader struct {
    p *pipe
}

// Read implements the standard Read interface:
// it reads data from the pipe, blocking until a writer
// arrives or the write end is closed.
// If the write end is closed with an error, that error is
// returned as err; otherwise err is EOF.
func (r *PipeReader) Read(data []byte) (n int, err error) {
    return r.p.Read(data)
}

// Close closes the reader; subsequent writes to the
// write half of the pipe will return the error ErrClosedPipe.
func (r *PipeReader) Close() error {
    return r.CloseWithError(nil)
}

// CloseWithError closes the reader; subsequent writes
// to the write half of the pipe will return the error err.
//
// CloseWithError never overwrites the previous error if it exists
// and always returns nil.
func (r *PipeReader) CloseWithError(err error) error {
    return r.p.CloseRead(err)
}

// A PipeWriter is the write half of a pipe.
type PipeWriter struct {
    p *pipe
}

// Write implements the standard Write interface:
// it writes data to the pipe, blocking until one or more readers
// have consumed all the data or the read end is closed.
// If the read end is closed with an error, that err is
// returned as err; otherwise err is ErrClosedPipe.
func (w *PipeWriter) Write(data []byte) (n int, err error) {
    return w.p.Write(data)
}

// Close closes the writer; subsequent reads from the
// read half of the pipe will return no bytes and EOF.
func (w *PipeWriter) Close() error {
    return w.CloseWithError(nil)
}

// CloseWithError closes the writer; subsequent reads from the
// read half of the pipe will return no bytes and the error err,
// or EOF if err is nil.
//
// CloseWithError never overwrites the previous error if it exists
// and always returns nil.
func (w *PipeWriter) CloseWithError(err error) error {
    return w.p.CloseWrite(err)
}

// Pipe creates a synchronous in-memory pipe.
// It can be used to connect code expecting an io.Reader
// with code expecting an io.Writer.
//
// Reads and Writes on the pipe are matched one to one
// except when multiple Reads are needed to consume a single Write.
// That is, each Write to the PipeWriter blocks until it has satisfied
// one or more Reads from the PipeReader that fully consume
// the written data.
// The data is copied directly from the Write to the corresponding
// Read (or Reads); there is no internal buffering.
//
// It is safe to call Read and Write in parallel with each other or with Close.
// Parallel calls to Read and parallel calls to Write are also safe:
// the individual calls will be gated sequentially.
// 构造一个包含读端和写端的pipe
// 通过Pipe获得一个可以立即使用的pipe
func Pipe() (*PipeReader, *PipeWriter) {
    p := &pipe{
        wrCh: make(chan []byte),
        rdCh: make(chan int),
        done: make(chan struct{}),
    }
    return &PipeReader{p}, &PipeWriter{p}
}

总结

pipe最核心还是通过channel来进行通信,利用无缓冲channel实现了读端和写端的阻塞等待和唤醒,同时通过记录读取字节数和锁实现了顺序流式数据传递的管道,并对外提供了构建pipe的能力,支持开箱即用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 推荐阅读Go语言小贴士1 - io包Go语言小贴士2 - 协议解析Go语言小贴士3 - bufio包 一、《GO语...
    合肥黑阅读 13,000评论 0 12
  • 在整个Java.io包中最重要的就是5个类和一个接口。 5个类指的是 File OutputStream Inpu...
    悟空嘿阅读 891评论 0 0
  • Java中I/O操作主要是指使用Java进行输入,输出操作. Java所有的I/O机制都是基于数据流进行输入输出,...
    cmlong_阅读 490评论 0 4
  • 虽然写了一些javaIo流的总结,感觉依旧没有系统的了解javaIO,幸好从网上看到这一篇文章,觉得不错。整理记录...
    Marlon666阅读 355评论 0 2
  • 1. File 类的作用? File类是java.io包下代表与平台无关的文件和目录,通过File可以操作文件和目...
    一叶知休阅读 201评论 0 1