Golangエントリ:チャンネル(チャンネル)

著者「Golang入門:競争条件は、」紙は、競争Golang並行プログラミングの条件に直面する必要が導入されました。この記事では、Golangが競争を排除するために提供されたチャネル(チャネル)を使用する方法について説明します。

チャネルが設けGolangゴルーチン言語レベルとの間の通信に2つの以上のゴルーチン間のメッセージ転送チャネルで使用することができます。チャネルが通信中であるので、より一貫したプロセスと動作を呼び出すチャネルパラメータ伝達関数によって送信さオブジェクト、そのようなポインタが渡されてもよいです。必要なリソースを送受信するために、共有チャネルを使用して、ゴルーチン間の競合状態を排除することができます。

リソースは、ゴルーチン間で共有する必要がある場合、ゴルーチン間のチャネルは、パイプを設置し、データ同期の交換を確実にするメカニズムを提供します。チャネルタイプは、チャネルは、一種類のみの値を渡すことができ、たとえば、関連付けられている、あなたはチャンネルを宣言する際に型を指定する必要があります。チャネルは、ビルトインタイプ、名前付きの型、構造型、および参照またはポインタ値タイプで共有することができます。

基本的な構文

構文文のチャネルである:
VARのChannelNameはちゃんのElementType

そして、一般的な変数は、唯一の違いは前にある宣言された型のキーワードちゃんが追加されます。送信することができるこのタイプのElementTypeチャネルデータが示されています。例えば、チャネル伝達のint型を宣言する。

VaRの -chチャンint型

またはその要素がbool型チャネルのあるマップを、宣言します。

VaRの [メートルマップ文字列へ ]チャンBOOL

あなたはGolangチャンネル内のクラスのインスタンスを作成するために、組み込み関数の使用を行う必要があります。

CH:=(ちゃん作るint型

これは、CHと呼ばれるチャネルのint型を宣言し、初期化します。チャネル構文を使用してデータを送受信するチャネルにデータを送信するために、次のコードとして、簡単です。

CH < - 値

チャネルへのデータの書き込みは、一般的に、他のゴルーチンチャネルからデータを読み込むまで、プログラムブロックの原因となります。以下のコードは、変数へのデータチャネルから読み出されます。

値:= <-ch

チャンネルがある場合、データはチャネルから読み込まれるデータが、また、データが書き込まれるまでチャンネルまでブロックされたプログラムを起こさないことに注意してください。

いずれかのバッファが単にバッファとチャネルとチャネルフリーバッファにチャンネルができた場合にチャンネルをよると、本論文では以下のページから詳細にチャネルの使用、これらの2つのタイプを説明します。

選択

ファイルのシリーズを監視するためのLinuxシステムの選択機能は、I / O操作は、選択機能のリターンを発生し、ファイルハンドル一度に処理します。この機能は、主に、高度に同時ソケットサーバプログラムを実装するために使用されます。少し似て選択したキーワードとLinuxで関数function Golangを選択し、主に、非同期I / Oの問題を処理するために使用されます。

構文は、[スタート]新しい選択ブロック、case文を説明した選択条件の各選択スイッチと非常によく似ています。switch文は、等価比較の条件のいずれかを選択するために使用することができると比較して、最大限界よりSELECT限界があり、それぞれの場合にステートメントは、I / O操作でなければなりません。これは次のように一般的に構成されています。

SELECT {
     ケース <-chan1:        // CHAN1が正常にデータを読み込む場合は、case文の実行
    ケース CHAN2 < -を1。 // もしCHAN2、ケースステートメントの実行に成功した書き込み
    デフォルト// 上記の場合条件は成功していない、プロセス実行のデフォルト 
}

それは、SELECTスイッチとは異なり、遅くとも決意条件が、直接case文ビューに見られるように。各ケースは、チャネルの操作のための文でなければなりません。例えば上記の例のように、CHAN1からデータを読み出し、読み出したデータを無視しようとし、第2のケースの両方が成功しない場合、chan2整数1を書き込みしようとしている場合、デフォルトの文が実行されます。

バッファなしチャンネル

前に任意の値を受信するチャンネルを保存する機能はありませんバッファリングされていないチャネル(バッファリングされていないチャネル)。ゴルーチンを送受信するために必要なチャネルのこのタイプは、送信を完了し、操作を受信する準備ができて、同じ時間をゴルーチン。同時に2つのゴルーチンの準備ができていない場合は、チャネルは、第一のブロック待ちの動作を送信または受信ゴルーチンの実現につながります。このインタラクティブ送信の動作と受信チャネルは、それ自体が同期されます。前記のいずれかの操作を離れて他から一人で操作することはできません。実例として、我々は、以下の2つのバッファなしゴルーチンのイメージが共有チャネル値(インターネットからの図)を使用する方法を理解することができます。

下面详细地解释一下上图:

  • 在第 1 步,两个 goroutine 都到达通道,但两个都没有开始执行数据的发送或接收。
  • 在第 2 步,左侧的 goroutine 将它的手伸进了通道,这模拟了向通道发送数据的行为。这时,这个 goroutine 会在通道中被锁住,直到交换完成。
  • 在第 3 步,右侧的 goroutine 将它的手放入通道,这模拟了从通道里接收数据。这个 goroutine一样也会在通道中被锁住,直到交换完成。
  • 在第 4 步和第 5 步,进行数据交换。
  • 在第 6 步,两个 goroutine 都将它们的手从通道里拿出来,这模拟了被锁住的 goroutine 得到释放。两个 goroutine 现在都可以去做别的事情了。

下面的例子模拟一场网球比赛。在网球比赛中,两位选手会把球在两个人之间来回传递。选手总是处在以下两种状态之一:要么在等待接球,要么将球打向对方。可以使用两个goroutine来模拟网球比赛,并使用无缓冲的通道来模拟球的来回:

// 这个示例程序展示如何用无缓冲的通道来模拟
//2个goroutine间的网球比赛
package main

import(
    "math/rand"
    "sync"
    "time"
    "fmt"
)

// wg用来等待程序结束
var wg sync.WaitGroup

func init() {
    rand.Seed(time.Now().UnixNano())
}

// main是所有Go程序的入口
func main() {
    // 创建一个无缓冲的通道
    court := make(chan int)

    // 计数加2,表示要等待两个goroutine
    wg.Add(2)

    // 启动两个选手
    go player("Nick", court)
    go player("Jack", court)

    // 发球
    court <- 1

    // 等待游戏结束
    wg.Wait()
}

// player 模拟一个选手在打网球
func player(name string, court chan int) {
    // 在函数退出时调用Done来通知main函数工作已经完成
    defer wg.Done()

    for{
        // 等待球被击打过来
        ball, ok := <-court
        if !ok {
            // 如果通道被关闭,我们就赢了
            fmt.Printf("Player %s Won\n", name)
            return
        }

        // 选随机数,然后用这个数来判断我们是否丢球
        n := rand.Intn(100)
        if n%5 == 0 {
            fmt.Printf("Player %s Missed\n", name)

            // 关闭通道,表示我们输了
            close(court)
            return
        }

        // 显示击球数,并将击球数加1
        fmt.Printf("Player %s Hit %d\n", name, ball)
        ball++

        // 将球打向对手
        court <- ball
    }
}

运行上面的代码,会输出类似下面的信息:

Player Jack Hit 1
Player Nick Hit 2
Player Jack Hit 3
Player Nick Hit 4
Player Jack Missed
Player Nick Won

简单解释一下上面的代码:
在 main 函数中创建了一个 int 类型的无缓冲的通道,使用该通道让两个 goroutine 在击球时能够互相同步。然后创建了参与比赛的两个 goroutine。在这个时候,两个 goroutine 都阻塞住等待击球。court <- 1 模拟发球,将球发到通道里,程序开始执行这个比赛,直到某个 goroutine 输掉比赛。
在 player 函数里,主要是运行一个无限循环的 for 语句。在这个循环里,是玩游戏的过程。goroutine 从通道接收数据,用来表示等待接球。这个接收动作会锁住 goroutine,直到有数据发送到通道里。通道的接收动作返回时,会检测 ok 标志是否为 false。如果这个值是 false,表示通道已经被关闭,游戏结束。在这个模拟程序中,使用随机数来决定 goroutine 是否击中了球。如果击中了球,就把 ball 的值递增 1,并将 ball 作为球重新放入通道,发送给另一位选手。在这个时刻,两个 goroutine 都会被锁住,直到交换完成。最终,引某个 goroutine 没有打中球会把通道关闭。之后两个 goroutine 都会返回,通过 defer 声明的 Done 会被执行,程序终止。

带缓冲的 channel

带缓冲的 channel(buffered channel) 是一种在被接收前能存储一个或者多个值的通道。这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道没有这种保证。可以通过下面的图示形象地理解两个 goroutine 分别向带缓冲的通道里增加一个值和从带缓冲的通道里移除一个值(下图来自互联网):

下面详细地解释一下上图:

  • 在第 1 步,右侧的 goroutine 正在从通道接收一个值。
  • 在第 2 步,右侧的这个 goroutine 独立完成了接收值的动作,而左侧的 goroutine 正在发送一个新值到通道里。
  • 在第 3 步,左侧的 goroutine 还在向通道发送新值,而右侧的 goroutine 正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。
  • 最后,在第 4 步,所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。

创建带缓冲区的 channel 非常简单,只需要再添加一个缓冲区的大小就可以了,比如创建一个传递 int 类型数据,缓冲区为 10 的 channel:

ch := make(chan int, 10)

下面的 demo 使用一组 goroutine 来接收并完成任务,带缓冲区的通道提供了一种清晰而直观的方式来实现这个功能:

// 这个示例程序展示如何使用
// 有缓冲的通道和固定数目的
// goroutine来处理一堆工作
package main

import(
    "math/rand"
    "sync"
    "time"
    "fmt"
)

const(
    numberGoroutines = 2 // 要使用的goroutine的数量
    taskLoad = 5 // 要处理的工作的数量
)

// wg用来等待程序结束
var wg sync.WaitGroup

func init()  {
    rand.Seed(time.Now().UnixNano())
}

// main是所有Go程序的入口
func main()  {
    // 创建一个有缓冲的通道来管理工作
    tasks := make(chan string, taskLoad)
    
    // 启动goroutine来处理工作
    wg.Add(numberGoroutines)
    for gr := 1; gr <= numberGoroutines; gr++ {
        go worker(tasks, gr)
    }
    
    // 增加一组要完成的工作
    for post := 1; post <= taskLoad; post++ {
        tasks <- fmt.Sprintf("Task: %d", post)
    }
    
    // 当所有工作都处理完时关闭通道
    // 以便所有goroutine退出
    close(tasks)
    
    // 等待所有工作完成
    wg.Wait()
}

// worker作为goroutine启动来处理
// 从有缓冲的通道传入的工作
func worker(tasks chan string, worker int) {
    // 通知函数已经返回
    defer wg.Done()

    for{
        // 等待分配工作
        task, ok := <-tasks
        if !ok{
            // 这意味着通道已经空了,并且已被关闭
            fmt.Printf("Worker: %d: Shutting Down\n", worker)
            return
        }

        // 显示我们开始工作了
        fmt.Printf("Worker: %d: Started %s\n", worker, task)

        // 随机等一段时间来模拟工作
        sleep := rand.Int63n(100)
        time.Sleep(time.Duration(sleep)* time.Millisecond)

        // 显示我们完成了工作
        fmt.Printf("Worker: %d: Completed %s\n", worker, task)
    }
}

运行上面的程序,输出结果大致如下:

Worker: 2: Started Task: 1
Worker: 1: Started Task: 2
Worker: 1: Completed Task: 2
Worker: 1: Started Task: 3
Worker: 1: Completed Task: 3
Worker: 1: Started Task: 4
Worker: 2: Completed Task: 1
Worker: 2: Started Task: 5
Worker: 1: Completed Task: 4
Worker: 1: Shutting Down
Worker: 2: Completed Task: 5
Worker: 2: Shutting Down

代码里有很详细的注释,因此不再赘言,只解释一下通道的关闭:
关闭通道的代码非常重要。当通道关闭后,goroutine 依旧可以从通道接收数据,但是不能再向通道里发送数据。能够从已经关闭的通道接收数据这一点非常重要,因为这允许通道关闭后依旧能取出其中缓冲的全部值,而不会有数据丢失。从一个已经关闭且没有数据的通道里获取数据,总会立刻返回,并返回一个通道类型的零值。如果在获取通道时还加入了可选的标志,就能得到通道的状态信息。

处理超时

使用 channel 时需要小心,比如对于下面的简单用法:

i := <-ch

碰到永远没有往 ch 中写入数据的情况,那么这个读取动作将永远也无法从 ch 中读取到数据,导致的结果就是整个 goroutine 永远阻塞并且没有挽回的机会。如果 channel 只是被同一个开发者使用,那样出问题的可能性还低一些。但如果一旦对外公开,就必须考虑到最差情况并对程序进行维护。

Golang 没有提供直接的超时处理机制,但可以利用 select 机制变通地解决。因为 select 的特点是只要其中一个 case 已经完成,程序就会继续往下执行,而不会考虑其它的 case。基于此特性我们来实现一个 channel 的超时机制:

ch := make(chan int)
// 首先实现并执行一个匿名的超时等待函数
timeout := make(chan bool, 1)
go func() {
    time.Sleep(1e9) // 等待 1 秒
    timeout <- true
}()
// 然后把 timeout 这个 channel 利用起来
select {
case <-ch:
    // 从 ch 中读取到数据
case <- timeout:
    // 一直没有从 ch 中读取到数据,但从 timeout 中读取到了数据
    fmt.Println("Timeout occurred.")
}

执行上面的代码,输出的结果为:

Timeout occurred.

关闭 channel

关闭 channel 非常简单,直接调用 Golang 内置的 close() 函数就可以了:

close(ch)

在关闭了 channel 之后我们要面对的问题是:如何判断一个 channel 是否已关闭?
其实在从 channel 中读取数据的同时,还可以获得一个布尔类型的值,该值表示 channel 是否已关闭:

x, ok := <-ch

如果 ok 的值为 false,则表示 ch 已经被关闭。

参考:
《Go语言实战》
《Go语言编程入门与实战技巧》

おすすめ

転載: www.cnblogs.com/sparkdev/p/10988499.html