Pop Out Code

    1. // Copyright 2010 The Go Authors. All rights reserved.
    2. // Use of this source code is governed by a BSD-style
    3. // license that can be found in the LICENSE file.
    4. package main
    5. import (
    6. "log"
    7. "net/http"
    8. "time"
    9. )
    10. const (
    11. numPollers = 2 // number of Poller goroutines to launch
    12. pollInterval = 60 * time.Second // how often to poll each URL
    13. statusInterval = 10 * time.Second // how often to log status to stdout
    14. errTimeout = 10 * time.Second // back-off timeout on error
    15. )
    16. var urls = []string{
    17. "http://www.google.com/",
    18. "http://golang.org/",
    19. "http://blog.golang.org/",
    20. }
    21. // State represents the last-known state of a URL.
    22. type State struct {
    23. url string
    24. status string
    25. }
    26. // StateMonitor maintains a map that stores the state of the URLs being
    27. // polled, and prints the current state every updateInterval nanoseconds.
    28. // It returns a chan State to which resource state should be sent.
    29. func StateMonitor(updateInterval time.Duration) chan<- State {
    30. updates := make(chan State)
    31. urlStatus := make(map[string]string)
    32. ticker := time.NewTicker(updateInterval)
    33. go func() {
    34. for {
    35. select {
    36. case <-ticker.C:
    37. logState(urlStatus)
    38. case s := <-updates:
    39. urlStatus[s.url] = s.status
    40. }
    41. }
    42. }()
    43. return updates
    44. }
    45. // logState prints a state map.
    46. func logState(s map[string]string) {
    47. log.Println("Current state:")
    48. for k, v := range s {
    49. log.Printf(" %s %s", k, v)
    50. }
    51. }
    52. // Resource represents an HTTP URL to be polled by this program.
    53. type Resource struct {
    54. url string
    55. errCount int
    56. }
    57. // Poll executes an HTTP HEAD request for url
    58. // and returns the HTTP status string or an error string.
    59. func (r *Resource) Poll() string {
    60. resp, err := http.Head(r.url)
    61. if err != nil {
    62. log.Println("Error", r.url, err)
    63. r.errCount++
    64. return err.Error()
    65. }
    66. r.errCount = 0
    67. return resp.Status
    68. }
    69. // Sleep sleeps for an appropriate interval (dependent on error state)
    70. // before sending the Resource to done.
    71. func (r *Resource) Sleep(done chan<- *Resource) {
    72. time.Sleep(pollInterval + errTimeout*time.Duration(r.errCount))
    73. done <- r
    74. }
    75. func Poller(in <-chan *Resource, out chan<- *Resource, status chan<- State) {
    76. for r := range in {
    77. s := r.Poll()
    78. status <- State{r.url, s}
    79. out <- r
    80. }
    81. }
    82. func main() {
    83. // Create our input and output channels.
    84. pending, complete := make(chan *Resource), make(chan *Resource)
    85. // Launch the StateMonitor.
    86. status := StateMonitor(statusInterval)
    87. // Launch some Poller goroutines.
    88. for i := 0; i < numPollers; i++ {
    89. go Poller(pending, complete, status)
    90. }
    91. // Send some Resources to the pending queue.
    92. go func() {
    93. for _, url := range urls {
    94. pending <- &Resource{url: url}
    95. }
    96. }()
    97. for r := range complete {
    98. go r.Sleep(pending)
    99. }
    100. }


    はじめに

    Goの並行性へのアプローチは、従来のスレッドと共有メモリの使用とは異なります。哲学的には、次のように要約できます:

    メモリを共有することで通信しないでください。通信することでメモリを共有してください。

    チャネルを使用すると、goroutine間でデータ構造への参照を渡すことができます。これをデータの所有権(読み書きの能力)を渡すことと考えると、強力で表現力豊かな同期メカニズムになります。

    このコードウォークでは、URLのリストをポーリングし、HTTPレスポンスコードを確認し、定期的にその状態を印刷するシンプルなプログラムを見ていきます。

    doc/codewalk/urlpoll.go


    状態タイプ

    Stateタイプは、URLの状態を表します。

    PollerはState値をStateMonitorに送信し、各URLの現在の状態のマップを維持します。

    doc/codewalk/urlpoll.go:26,30


    リソースタイプ

    Resourceは、ポーリングされるURLの状態を表します:URL自体と、最後の成功したポーリング以来に遭遇したエラーの数です。

    プログラムが開始されると、各URLに対して1つのResourceが割り当てられます。メインのgoroutineとPollerのgoroutineは、チャネルを介してリソースを互いに送信します。

    doc/codewalk/urlpoll.go:60,64


    Poller関数

    各Pollerは、入力チャネルからResourceポインタを受け取ります。このプログラムでは、チャネルでResourceポインタを送信することは、送信者から受信者への基礎データの所有権を渡すことを意味します。この慣習により、2つのgoroutineが同時にこのResourceにアクセスすることはないとわかります。これは、これらのデータ構造への同時アクセスを防ぐためにロックを心配する必要がないことを意味します。

    Pollerは、ResourceのPollメソッドを呼び出してResourceを処理します。

    Pollの結果をStateMonitorに通知するために、ステータスチャネルにState値を送信します。

    最後に、Resourceポインタをoutチャネルに送信します。これは、Pollerが「このResourceは完了しました」と言って、メインのgoroutineに所有権を返すことを解釈できます。

    複数のgoroutineがPollerを実行し、リソースを並行して処理します。

    doc/codewalk/urlpoll.go:86,92


    Pollメソッド

    Pollメソッド(Resourceタイプの)は、ResourceのURLに対してHTTP HEADリクエストを実行し、HTTPレスポンスのステータスコードを返します。エラーが発生した場合、Pollはメッセージを標準エラーにログし、代わりにエラーストリングを返します。

    doc/codewalk/urlpoll.go:66,77


    メイン関数

    メイン関数は、PollerとStateMonitorのgoroutineを開始し、適切な遅延の後に完了したリソースを保留チャネルに戻すループを実行します。

    doc/codewalk/urlpoll.go:94,116


    チャネルの作成

    まず、メインは*Resourceの2つのチャネル、pendingとcompleteを作成します。

    メイン内で、新しいgoroutineが各URLに対して1つのResourceをpendingに送信し、メインのgoroutineがcompleteから完了したResourceを受信します。

    pendingとcompleteのチャネルは、各Pollerのgoroutineに渡され、その中ではinとoutとして知られています。

    doc/codewalk/urlpoll.go:95,96


    StateMonitorの初期化

    StateMonitorは、各Resourceの状態を保存するgoroutineを初期化して起動します。この関数については後で詳しく見ていきます。

    現時点では、重要なことは、Stateのチャネルを返し、それがstatusとして保存され、Pollerのgoroutineに渡されることです。

    doc/codewalk/urlpoll.go:98,99


    Pollerのgoroutineの起動

    必要なチャネルが揃ったので、メインは複数のPollerのgoroutineを起動し、チャネルを引数として渡します。チャネルは、メイン、Poller、およびStateMonitorのgoroutine間の通信手段を提供します。

    doc/codewalk/urlpoll.go:101,104


    リソースを保留に送信

    システムに初期作業を追加するために、メインは新しいgoroutineを開始し、各URLに対して1つのResourceをpendingに割り当てて送信します。

    新しいgoroutineが必要なのは、バッファなしのチャネルの送信と受信が同期的であるためです。つまり、これらのチャネル送信は、Pollerがpendingから読み取る準備ができるまでブロックされます。

    これらの送信がメインのgoroutineで行われ、チャネル送信よりも少ないPollerがある場合、プログラムはデッドロック状態に達します。なぜなら、メインはまだcompleteから受信していないからです。

    読者への課題:このプログラムのこの部分を修正して、ファイルからURLのリストを読み取るようにしてください。(このgoroutineを独自の名前付き関数に移動することを検討してもよいでしょう。)

    doc/codewalk/urlpoll.go:106,111


    メインイベントループ

    PollerがResourceの処理を完了すると、それをcompleteチャネルに送信します。このループは、completeからそのResourceポインタを受信します。受信した各Resourceについて、ResourceのSleepメソッドを呼び出す新しいgoroutineを開始します。各Resourceに新しいgoroutineを使用することで、スリープが並行して発生できるようにします。

    単一のResourceポインタは、同時にpendingまたはcompleteのいずれかにのみ送信されることに注意してください。これにより、ResourceはPollerのgoroutineによって処理されるか、スリープしているかのいずれかであり、同時に両方ではないことが保証されます。このようにして、通信によってResourceデータを共有します。

    doc/codewalk/urlpoll.go:113,115


    Sleepメソッド

    Sleepは、Resourceをdoneに送信する前に一時停止するためにtime.Sleepを呼び出します。この一時停止は、固定の長さ(pollInterval)と、連続エラーの数(r.errCount)に比例した追加の遅延のいずれかになります。

    これは、goroutine内で実行されることを意図した関数がチャネルを受け取り、そのチャネルに戻り値(または完了状態の他の指示)を送信するという典型的なGoのイディオムの例です。

    doc/codewalk/urlpoll.go:79,84


    StateMonitor

    StateMonitorは、チャネルでState値を受信し、プログラムによってポーリングされているすべてのResourceの状態を定期的に出力します。

    doc/codewalk/urlpoll.go:32,50


    updatesチャネル

    変数updatesはStateのチャネルであり、PollerのgoroutineがState値を送信します。

    このチャネルは関数によって返されます。

    doc/codewalk/urlpoll.go:36


    urlStatusマップ

    変数urlStatusは、URLとその最新のステータスのマップです。

    doc/codewalk/urlpoll.go:37


    Tickerオブジェクト

    time.Tickerは、指定された間隔でチャネルに値を繰り返し送信するオブジェクトです。

    この場合、tickerはupdateIntervalナノ秒ごとに現在の状態を標準出力に印刷するトリガーとなります。

    doc/codewalk/urlpoll.go:38


    StateMonitorのgoroutine

    StateMonitorは永遠にループし、ticker.Cとupdateの2つのチャネルで選択します。select文は、その通信のいずれかが進行する準備ができるまでブロックされます。

    StateMonitorがticker.Cからのティックを受信すると、logStateを呼び出して現在の状態を印刷します。updatesからStateの更新を受信すると、urlStatusマップに新しいステータスを記録します。

    このgoroutineはurlStatusデータ構造を所有しているため、逐次的にのみアクセスできることに注意してください。これにより、共有マップへの並行読み取りおよび/または書き込みから生じる可能性のあるメモリ破損の問題が防止されます。

    doc/codewalk/urlpoll.go:39,48


    結論

    このコードウォークでは、Goの並行性プリミティブを使用して、通信を通じてメモリを共有するシンプルな例を探求しました。

    これは、goroutineとチャネルを使用して表現力豊かで簡潔な並行プログラムを書く方法を探求するための出発点を提供するはずです。

    doc/codewalk/urlpoll.go