いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-450 (mkfifoメモ)(4)(read-nonblocking)

関連記事

Goメモ-447 (mkfifoメモ)(1)(create) - いろいろ備忘録日記

Goメモ-448 (mkfifoメモ)(2)(read) - いろいろ備忘録日記

Goメモ-449 (mkfifoメモ)(3)(write) - いろいろ備忘録日記

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。忘れないうちにメモメモ。。。

Go側から名前付きパイプ(mkfifo)を扱うことがあったので、忘れない内にメモしておこうと思いました。

今回は名前付きパイプファイルからの読み込み(ノンブロッキングモード)について。

なお、標準ライブラリにある syscall パッケージの Mkfifo() でも行えるのですが

サンプルでは sys/unix の方を使っています。

サンプル

//go:build linux

package main

import (
    "bufio"
    "errors"
    "flag"
    "fmt"
    "io"
    "log"
    "os"
    "sync"
    "time"

    "golang.org/x/sys/unix"
)

var (
    fname string
)

func init() {
    log.SetFlags(log.Lmicroseconds)

    flag.StringVar(&fname, "fname", "", "FIFO file name")
    flag.Parse()
}

func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() error {
    //
    // 名前付きパイプをノンブロッキングモードで開く
    //   明示的なノンブロッキングモードの指定は os.OpenFile() では行えないため
    //   golang.org/x/sys/unix を利用する
    //
    var (
        fd  int
        f   *os.File
        err error
    )

    log.Println("[Before] unix.Open(unix.O_RDONLY|unix.O_NONBLOCK)")

    fd, err = unix.Open(fname, unix.O_RDONLY|unix.O_NONBLOCK, 0666)
    if err != nil {
        return err
    }

    f = os.NewFile(uintptr(fd), fname)
    if f == nil {
        return fmt.Errorf("invalid file descriptor")
    }
    defer f.Close() // ここで f.Close() しているので、上で unix.Close(fd) は不要

    log.Println("[After ] unix.Open(unix.O_RDONLY|unix.O_NONBLOCK)")

    //
    // データを読み取り
    //
    type (
        data struct {
            value string
            err   error
        }
    )
    var (
        reader  = bufio.NewReader(f)
        lines   = make(chan data)
        timeout = 1500 * time.Millisecond
        done    = make(chan struct{})
        wg      sync.WaitGroup
    )

    wg.Add(1)
    go func() {
        defer wg.Done()

        // ノンブロッキングモードで処理しているため、データが存在しない場合は即EOFが返ってくる
        for {
            line, err := reader.ReadString('\n')
            if err != nil && errors.Is(err, io.EOF) {
                log.Println("読み取れるデータが存在しない")

                select {
                case <-done:
                    return
                case <-time.After(200 * time.Millisecond):
                    continue
                }
            }

            lines <- data{line, err}
            return
        }
    }()

    select {
    case line := <-lines:
        if line.err != nil {
            return line.err
        }

        log.Println(line.value)
    case <-time.After(timeout):
        log.Println("timeout")
    }

    close(done)
    wg.Wait()

    return nil
}

実行すると以下のようになります。

$ task
task: [build] go build -o app .
task: [create-fifo] rm -f ./tmp-fifo
task: [create-fifo] mkfifo ./tmp-fifo -m0666
task: [run] (sleep 1; echo "helloworld" > ./tmp-fifo) &
task: [run] ./app -fname ./tmp-fifo
05:25:35.397135 [Before] unix.Open(unix.O_RDONLY|unix.O_NONBLOCK)
05:25:35.397320 [After ] unix.Open(unix.O_RDONLY|unix.O_NONBLOCK)
05:25:35.397368 読み取れるデータが存在しない
05:25:35.597705 読み取れるデータが存在しない
05:25:35.798143 読み取れるデータが存在しない
05:25:35.998529 読み取れるデータが存在しない
05:25:36.198859 読み取れるデータが存在しない
05:25:36.399736 helloworld

ノンブロッキングモードでオープンしているので、ファイルを開く部分ではブロックされなくなります。

代わりに Read する部分にて、まだ読み取れるデータが存在しない場合に即 io.EOF が返ってきます。

参考情報

6.3 Named Pipes (FIFOs - First In First Out)

Ubuntu Manpage: mkfifo, mkfifoat - FIFOスペシャルファイル(名前付きパイプ)を作成する

Master the Linux ‘mkfifo’ Command: A Comprehensive Guide | by Peter Hou | Medium

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。