62

私は Go を学んでいますが、これまでのところ非常に感銘を受けています。golang.org のすべてのオンライン ドキュメントを読み、Chrisnall の「The Go Programming Language Phrasebook」の途中まで読みました。チャネルの概念を理解し、非常に役立つと思います。ただし、一方通行チャネルのポイントがわからないため、途中で重要な何かを見逃したに違いありません。

私がそれらを正しく解釈している場合、読み取り専用チャネルは受信のみが可能で、書き込み専用チャネルは送信のみが可能です。ある「方向」から別の方向にキャストできますか? もしそうなら、また、実際の制約がない場合、ポイントは何ですか? それらはチャネルの目的のクライアント コードへのヒントにすぎませんか?

4

3 に答える 3

100

チャネルは、受信者に対して読み取り専用にすることができますが、送信者には書き込み可能な双方向チャネルがあります。例えば:

func F() <-chan int {
    // Create a regular, two-way channel.
    c := make(chan int)

    go func() {
        defer close(c)

        // Do stuff
        c <- 123
    }()

    // Returning it, implicitely converts it to read-only,
    // as per the function return value.
    return c
}

を呼び出す人は誰でもF()、読み取り専用のチャネルを受け取ります。これは、コンパイル時にチャネルが誤用される可能性を回避するのに最も役立ちます。読み取り/書き込み専用チャネルは異なる型であるため、コンパイラは既存の型チェック メカニズムを使用して、呼び出し元がビジネス書き込みのないチャネルに何かを書き込もうとしないようにすることができます。

于 2012-11-28T01:48:53.197 に答える
9

読み取り専用チャネルの主な動機は、チャネルの破損とパニックを防ぐことだと思います。によって返されたチャネルに書き込むことができると想像してくださいtime.After。これにより、多くのコードが台無しになる可能性があります。

また、次の場合にパニックが発生する可能性があります。

  • チャンネルを複数回閉じる
  • 閉じたチャネルに書き込む

これらの操作は、読み取り専用チャネルのコンパイル時エラーですが、複数の go-routine がチャネルを書き込み/閉じることができる場合、厄介な競合状態を引き起こす可能性があります。

これを回避する 1 つの方法は、チャネルを閉じずにガベージ コレクションを実行することです。ただし、closeクリーンアップのためだけではなく、実際にはチャネルが範囲外の場合に使用されます。

func consumeAll(c <-chan bool) {
    for b := range c {
        ...
    }
}

チャネルが閉じられない場合、このループは終了しません。複数の go-routine が 1 つのチャネルに書き込みを行っている場合、どれがチャネルを閉じるかを決定するために、多くの簿記が必要になります。

読み取り専用チャネルを閉じることはできないため、これにより正しいコードを記述しやすくなります。@jimt が彼のコメントで指摘したように、読み取り専用チャネルを書き込み可能チャネルに変換することはできないため、チャネルの書き込み可能バージョンにアクセスできるコードの部分のみがチャネルを閉じる/書き込むことができることが保証されます。

編集:

複数の読者を持つことに関しては、あなたがそれを説明している限り、これはまったく問題ありません。これは、生産者/消費者モデルで使用する場合に特に役立ちます。たとえば、接続を受け入れてワーカー スレッドのキューに書き込むだけの TCP サーバーがあるとします。

func produce(l *net.TCPListener, c chan<- net.Conn) {
    for {
        conn, _ := l.Accept()
        c<-conn
    }
}

func consume(c <-chan net.Conn) {
    for conn := range c {
        // do something with conn
    }
}

func main() {
    c := make(chan net.Conn, 10)
    for i := 0; i < 10; i++ {
        go consume(c)
    }

    addr := net.TCPAddr{net.ParseIP("127.0.0.1"), 3000}
    l, _ := net.ListenTCP("tcp", &addr)
    produce(l, c)
}

接続処理は、新しい接続を受け入れるよりも時間がかかる可能性が高いため、単一のプロデューサーで多くのコンシューマーを使用する必要があります。複数のプロデューサーはより困難ですが (チャネルを閉じる人を調整する必要があるため)、チャネル送信にある種のセマフォ スタイルのチャネルを追加できます。

于 2012-11-28T18:45:48.883 に答える
6

Go チャネルは、Hoare の Communicating Sequential Processes に基づいてモデル化されています。これは、通信するアクター (小さな 'a') 間のイベント フローを中心とした同時実行のプロセス代数です。このように、チャネルには送信側と受信側、つまりイベントのプロデューサーとイベントのコンシューマーがあるため、方向があります。Occam と Limbo でも同様のモデルが使用されています。

これは重要です。チャネル エンドが任意に送信側と受信側の両方として異なる時間に再利用される可能性がある場合、デッドロックの問題について推論するのは困難です。

于 2012-11-28T09:57:25.903 に答える