Go ブログ
通信によってメモリを共有する
従来のスレッドモデル(例えば、Java、C++、Python プログラムを書く際に一般的に使用される)では、プログラマーは共有メモリを使用してスレッド間で通信する必要があります。通常、共有データ構造はロックによって保護され、スレッドはデータにアクセスするためにそれらのロックを争います。場合によっては、Python の Queue のようなスレッドセーフなデータ構造を使用することで、これが容易になります。
Go の並行処理プリミティブ - ゴルーチンとチャネル - は、並行ソフトウェアを構築するための洗練された明確な手段を提供します。(これらの概念は、C. A. R. Hoare の Communicating Sequential Processes に始まる 興味深い歴史 を持っています。)共有データへのアクセスを仲介するために明示的にロックを使用する代わりに、Go はゴルーチン間でデータへの参照を渡すためにチャネルを使用することを推奨しています。このアプローチにより、一度に 1 つのゴルーチンだけがデータにアクセスできるようになります。この概念は、ドキュメント Effective Go(Go プログラマー必読)にまとめられています。
メモリを共有することによって通信するのではなく、通信することによってメモリを共有しましょう。
URL のリストをポーリングするプログラムを考えてみましょう。従来のスレッド環境では、データ構造は次のようになります。
type Resource struct {
url string
polling bool
lastPolled int64
}
type Resources struct {
data []*Resource
lock *sync.Mutex
}
そして、Poller 関数(その多くは別々のスレッドで実行されます)は次のようになります。
func Poller(res *Resources) {
for {
// get the least recently-polled Resource
// and mark it as being polled
res.lock.Lock()
var r *Resource
for _, v := range res.data {
if v.polling {
continue
}
if r == nil || v.lastPolled < r.lastPolled {
r = v
}
}
if r != nil {
r.polling = true
}
res.lock.Unlock()
if r == nil {
continue
}
// poll the URL
// update the Resource's polling and lastPolled
res.lock.Lock()
r.polling = false
r.lastPolled = time.Nanoseconds()
res.lock.Unlock()
}
}
この関数は約 1 ページの長さで、完全にするにはさらに詳細が必要です。URL ポーリングロジック(それ自体は数行で済みます)も含まれておらず、リソースのプールを使い果たした場合にも適切に処理されません。
Go のイディオムを使用して実装された同じ機能を見てみましょう。この例では、Poller は入力チャネルからポーリングされるリソースを受信し、完了したら出力チャネルに送信する関数です。
type Resource string
func Poller(in, out chan *Resource) {
for r := range in {
// poll the URL
// send the processed Resource to out
out <- r
}
}
前の例の微妙なロジックは明らかに存在せず、Resource データ構造には簿記データが含まれなくなりました。実際、残っているのは重要な部分だけです。これは、これらの単純な言語機能の力を垣間見ることができます.
上記のコードスニペットには多くの省略があります。これらのアイデアを使用する完全で慣用的な Go プログラムのウォークスルーについては、コードウォーク 通信によるメモリ共有 を参照してください。
次の記事:Defer、Panic、および Recover
前の記事:Go の宣言構文
ブログインデックス