The Go Blog
コミュニケーションによってメモリを共有する
従来のスレッドモデル(例えばJava、C++、Pythonプログラムを記述する際によく使われる)では、プログラマは共有メモリを使ってスレッド間で通信を行う必要があります。通常、共有データ構造はロックによって保護され、スレッドはデータにアクセスするためにそれらのロックを競合します。場合によっては、PythonのQueueのようなスレッドセーフなデータ構造を使用することで、これが容易になります。
Goの並行処理プリミティブであるゴルーチンとチャネルは、並行ソフトウェアを構造化するためのエレガントで明確な手段を提供します。(これらの概念には、C. A. R. ホーアの並行プロセスの通信から始まる興味深い歴史があります。)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ポーリングロジック(それ自体は数行で済む)も含まれておらず、Resourcesのプールを使い果たした場合の優雅な処理もできません。
Goのイディオムを使って同じ機能を実装した例を見てみましょう。この例では、Pollerは入力チャネルからポーリングするResourceを受け取り、完了したら出力チャネルに送信する関数です。
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らしいプログラムのウォークスルーについては、Codewalk コミュニケーションによってメモリを共有するを参照してください。
次の記事:Defer、Panic、およびRecover
前の記事:Goの宣言構文
ブログインデックス