7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

チャンネルに送信可能かどうか判断する

Last updated at Posted at 2014-11-13

Goのチャンネルはクローズされていることが受信時には判断できますが送信時にはできません。
そのため、送信可能か判断できないチャンネルに送信する場合は一工夫必要になります。

問題のあるコード

以下のImpatientPrinter は入力された文字列を出力する機能を持っています。
ただし、1秒間入力がないと自動的に入力を閉じます。

package main

import (
	"fmt"
	"time"
)

type ImpatientPrinter struct {
	write chan string
}

func NewImpatientPrinter() ImpatientPrinter {
	w := make(chan string)

	go func() {
		for {
			select {
			case s := <-w:
				fmt.Println(s)
			case <-time.After(time.Second):
				close(w)
				return
			}
		}
	}()

	return ImpatientPrinter{
		write: w,
	}
}

func (p ImpatientPrinter) Write(s string) {
	p.write <- s
}

func main() {
	w := NewImpatientPrinter()
	w.Write("Hello, playground")

	time.Sleep(time.Second * 1)
	w.Write("Hello, playground")

	time.Sleep(time.Second * 1)
	w.Write("Hello, playground")
}

このコードの問題は、チャンネルに送信可能かどうかが外部から判別できないということです。
チャンネルを閉じた後にWriteしようとすると当然ながらpanicが発生します。
チャンネルをクローズしない場合でも、チャンネルを受信しているgoroutineが存在しないためデッドロックしてしまいます。

修正したコード

受信専用チャンネルをもう一つ追加し、そのチャンネルを代わりにクローズするのがシンプルな解決方法です。
送信が不可能になった後は受信専用チャンネルの受信がブロックしないため、Writeがどのgoroutineから実行されていても、送信可能かどうかが確実に判断できます。

package main

import (
	"errors"
	"fmt"
	"time"
)

type ImpatientPrinter struct {
	write  chan string
	closed chan int
}

func NewImpatientPrinter() ImpatientPrinter {
	w := make(chan string)
	c := make(chan int)

	go func() {
		for {
			select {
			case s := <-w:
				fmt.Println(s)
			case <-time.After(time.Second):
				close(c)
				return
			}
		}
	}()

	return ImpatientPrinter{
		write:  w,
		closed: c,
	}
}

func (p ImpatientPrinter) Write(s string) error {
	select {
	case p.write <- s:
	case <-p.closed:
		return errors.New("closed")
	}
	return nil
}

func main() {
	w := NewImpatientPrinter()
	w.Write("Hello, playground")

	time.Sleep(time.Second * 1)
	w.Write("Hello, playground")

	time.Sleep(time.Second * 1)
	w.Write("Hello, playground")
}

おまけ

禁断のrecover

package main

import (
	"errors"
	"fmt"
	"time"
)

type ImpatientPrinter struct {
	write chan string
}

func NewImpatientPrinter() ImpatientPrinter {
	w := make(chan string)

	go func() {
		for {
			select {
			case s := <-w:
				fmt.Println(s)
			case <-time.After(time.Second):
				close(w)
				return
			}
		}
	}()

	return ImpatientPrinter{
		write: w,
	}
}

func (p ImpatientPrinter) Write(s string) (e error) {
	defer func() {
		if r := recover(); r != nil {
			e = errors.New("closed")
		}
	}()
	p.write <- s
	return nil
}

func main() {
	w := NewImpatientPrinter()
	w.Write("Hello, playground")

	time.Sleep(time.Second * 1)
	w.Write("Hello, playground")

	time.Sleep(time.Second * 1)
	w.Write("Hello, playground")
}
7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?