2010年9月25日土曜日

channelを閉じる

channelのことを改めて調べたら、closeとclosedなんていう性質があることに気付いた。

  • channelはcloseすると以降のsendが無視されるようになる
  • そのあとそのchannelから一回値をreceiveすると、以降closedによる判定がtrueになる
  • 最後のreceiveで返ってくる値は0
この性質を利用して終了判定すれば、以前の記事で書いたようなdeferでブロック云々という問題がそもそも起こらない。しかも、channelへの入力が複数のgoroutineから行われてるときでも多分ブロックせずに正常終了できる。最初からこうすれば良かったかも。

サンプル


package main

import (
    "fmt"
)

type Message int

const (
    MSG_CLOSE Message = 0
)

type Output struct {
    data int
    response Response
}

type Response int

const (
    RESPONSE_OK Response = 0
)

type DataWithResponse struct{
    data int
    responseChannel chan Output
}

func Processor(msgChannel chan Message, inputChannel chan DataWithResponse) {
    defer func(){
        close(msgChannel)
        close(inputChannel)
    }()

    processing := true
    for processing {
        select {
        case msg := <- msgChannel:
            if msg == MSG_CLOSE {
                processing = false
            }
        case input := <- inputChannel:
            fmt.Println("Processing:",input.data)
            // process input here...
            outputData := input.data * 2
            input.responseChannel <- Output {
                outputData,
                RESPONSE_OK,
            }
        }
    }
}

func main() {
    msgChannel := make(chan Message)
    inputChannel := make(chan DataWithResponse)
    
    go Processor(msgChannel, inputChannel)

    // send input
    outputChannel := make(chan Output)
    input := DataWithResponse {
        123,
        outputChannel,
    }
    inputChannel <- input

    // receive output
    output := <- outputChannel
    fmt.Println("Processed:",output.data)

    if closed(msgChannel) {
        fmt.Println("Message channel closed 1")
    }

    msgChannel <- MSG_CLOSE

    // see what happens if we try to input more
    // while the channel is about to get closed
    inputChannel <- input
    msgChannel <- MSG_CLOSE
    inputChannel <- input
    msgChannel <- MSG_CLOSE
    inputChannel <- input
    msgChannel <- MSG_CLOSE

    if closed(msgChannel) {
        fmt.Println("Message channel closed 2")
    }
    msgClosed := <- msgChannel

    if msgClosed == MSG_CLOSE {
        fmt.Println("Closed:", msgClosed)
    }

    if closed(msgChannel) {
        fmt.Println("Message channel closed 3")
    }
}

出力

$ ./channel-test
Processing: 123
Processed: 246
Closed: 0
Message channel closed 3

出力を見ればわかるように、きっちり仕様通りになっている。closeが発行されたであろうタイミングでchannelに入力を入れてもブロックしてないし、closeされたchannelから値を読み出してはじめてclosedになってる。closeしたあとのchannelは値の0を返すようなので、CLOSE状態を判定する定数を0にしておけば可読性も良い。

ということでchannelのcloseは積極的に使いましょう。

おまけ

さっき気付いたけど、closeしたchannelにsendしすぎるとpanicが発生する。

変更点


    for {
        // see what happens if we try to input more
        // while the channel is about to get closed
        inputChannel <- input
        msgChannel <- MSG_CLOSE
        inputChannel <- input
        msgChannel <- MSG_CLOSE
        inputChannel <- input
        msgChannel <- MSG_CLOSE
    }

出力

$ ./channel-test
Processing: 123
Processed: 246
throw: too many operations on a closed channel

panic PC=0xb7615f14
(以下略)

0 件のコメント:

コメントを投稿