[インデックス 13708] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/rpc
パッケージにおいて、Server
構造体の serviceMap
フィールドを保護するために使用されるミューテックスを sync.Mutex
から sync.RWMutex
に変更するものです。これにより、RPCサーバーのサービスマップへの同時読み取りアクセスを許可し、パフォーマンスを向上させつつ、データの一貫性を維持します。
コミット
- コミットハッシュ:
e61c047c3e5ac3ad253c9046d479d769d14f7808
- 作者: Dmitriy Vyukov dvyukov@google.com
- コミット日時: 2012年8月30日 木曜日 20:32:32 +0400
- コミットメッセージ:
net/rpc: protect serviceMap with RWMutex R=r, r CC=golang-dev https://golang.org/cl/6494044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e61c047c3e5ac3ad253c9046d479d769d14f7808
元コミット内容
net/rpc: protect serviceMap with RWMutex
R=r, r
CC=golang-dev
https://golang.org/cl/6494044
変更の背景
Goの net/rpc
パッケージは、リモートプロシージャコール(RPC)を実装するための標準的な方法を提供します。RPCサーバーは、登録されたサービスとそのメソッドを管理するために serviceMap
というマップを使用します。このマップは複数のゴルーチンから同時にアクセスされる可能性があるため、データ競合を防ぐためにミューテックス(sync.Mutex
)で保護されていました。
しかし、serviceMap
へのアクセスパターンを考えると、読み取り操作(サービスの検索)が書き込み操作(サービスの登録/削除)よりもはるかに頻繁に行われることが予想されます。sync.Mutex
は排他的ロックであり、読み取り操作であっても他のすべての操作(読み取り、書き込み)をブロックします。これは、特に高負荷なRPCサーバーにおいて、serviceMap
への同時読み取りアクセスが頻繁に発生する場合に、不必要なボトルネックとなり、パフォーマンスの低下を招く可能性があります。
このコミットの背景には、このようなパフォーマンス上の制約を解消し、RPCサーバーのスケーラビリティと並行処理能力を向上させる目的があります。
前提知識の解説
Go言語の sync
パッケージ
Go言語の sync
パッケージは、プリミティブな同期メカニズムを提供し、並行処理におけるデータ競合を防ぐために使用されます。
-
sync.Mutex
:Mutex
は「相互排他ロック」を意味します。Lock()
メソッドでロックを取得し、Unlock()
メソッドでロックを解放します。- 一度に1つのゴルーチンのみがロックを保持できます。
- ロックが取得されている間は、他のすべてのゴルーチンは
Lock()
を呼び出すとブロックされ、ロックが解放されるまで待機します。 - 読み取りと書き込みの両方の操作に対して排他制御が必要な場合にシンプルで効果的ですが、読み取り操作が多い場合には並行性を損なう可能性があります。
-
sync.RWMutex
(Read-Write Mutex):RWMutex
は「読み書きロック」を意味します。- 読み取り操作と書き込み操作に対して異なるロックメカニズムを提供します。
- 読み取りロック (Read Lock):
RLock()
で取得し、RUnlock()
で解放します。複数のゴルーチンが同時に読み取りロックを保持できます。これにより、複数の読み取り操作が並行して実行されることが可能になります。 - 書き込みロック (Write Lock):
Lock()
で取得し、Unlock()
で解放します。sync.Mutex
と同様に、一度に1つのゴルーチンのみが書き込みロックを保持できます。書き込みロックが取得されている間は、他のすべての読み取りロックおよび書き込みロックの取得はブロックされます。 - 読み取り操作が書き込み操作よりもはるかに多い場合に、並行性を大幅に向上させることができます。
Go言語の net/rpc
パッケージ
net/rpc
パッケージは、Goプログラム間でRPC(Remote Procedure Call)を実装するためのメカニズムを提供します。これにより、別のプロセスやネットワーク上のマシンで実行されている関数を、あたかもローカル関数であるかのように呼び出すことができます。
- サーバー:
rpc.Register()
を使用してサービスオブジェクトを登録し、rpc.ServeConn()
やhttp.HandleRPC()
などでクライアントからのリクエストを待ち受けます。 - クライアント:
rpc.Dial()
を使用してサーバーに接続し、Client.Call()
メソッドでリモートプロシージャを呼び出します。
net/rpc
サーバー内部では、登録されたサービスを管理するためにマップ(serviceMap
)が使用されます。クライアントからのリクエストが来ると、このマップを検索して対応するサービスとメソッドを見つけます。
技術的詳細
このコミットの核心は、net/rpc
サーバーの Server
構造体内の mu
フィールドの型を sync.Mutex
から sync.RWMutex
に変更し、それに伴いロック操作を Lock()/Unlock()
から RLock()/RUnlock()
に変更した点です。
serviceMap
は、RPCサーバーに登録されたサービスを名前で管理するマップです。新しいサービスが登録される(書き込み操作)ことは比較的稀ですが、クライアントからのRPCリクエストが来るたびに、対応するサービスを serviceMap
から検索する(読み取り操作)必要があります。
sync.Mutex
を使用していた場合、サービス検索のような読み取り操作であっても、mu.Lock()
が呼び出されると、他のすべてのゴルーチンは mu.Unlock()
が呼び出されるまで待機する必要がありました。これは、複数のクライアントが同時にRPCリクエストを送信し、それぞれがサービス検索を行おうとする場合に、サービスマップへのアクセスが直列化され、並行性が失われることを意味します。
sync.RWMutex
に変更することで、以下の利点が得られます。
- 読み取り操作の並行性向上:
readRequestHeader
関数内でserviceMap
を検索する際にserver.mu.RLock()
とserver.mu.RUnlock()
が使用されるようになりました。これにより、複数のゴルーチンが同時にserviceMap
を読み取ることが可能になります。これは、RPCサーバーの主要なボトルネックの一つを解消し、特に読み取り負荷が高いシナリオでのスループットを大幅に向上させます。 - 書き込み操作の排他性維持: サービスの登録や削除といった
serviceMap
への書き込み操作は、引き続きserver.mu.Lock()
とserver.mu.Unlock()
を使用して排他的に保護されます。これにより、マップの変更中に読み取りや他の書き込みが行われることによるデータ破損を防ぎ、データの一貫性を保証します。
この変更は、net/rpc
サーバーのパフォーマンスとスケーラビリティを向上させるための重要な最適化です。
コアとなるコードの変更箇所
変更は src/pkg/net/rpc/server.go
ファイルにあります。
--- a/src/pkg/net/rpc/server.go
+++ b/src/pkg/net/rpc/server.go
@@ -182,7 +182,7 @@ type Response struct {
// Server represents an RPC Server.
type Server struct {
- mu sync.Mutex // protects the serviceMap
+ mu sync.RWMutex // protects the serviceMap
serviceMap map[string]*service
reqLock sync.Mutex // protects freeReq
freeReq *Request
@@ -539,9 +539,9 @@ func (server *Server) readRequestHeader(codec ServerCodec) (service *service, mt
return
}
// Look up the request.
- server.mu.Lock()
+ server.mu.RLock()
service = server.serviceMap[serviceMethod[0]]
- server.mu.Unlock()
+ server.mu.RUnlock()
if service == nil {
err = errors.New("rpc: can't find service " + req.ServiceMethod)
return
コアとなるコードの解説
-
Server
構造体のmu
フィールドの型変更:- mu sync.Mutex // protects the serviceMap + mu sync.RWMutex // protects the serviceMap
Server
構造体のmu
フィールドの型がsync.Mutex
からsync.RWMutex
に変更されました。これにより、mu
は読み書きロックの機能を持つようになります。コメント// protects the serviceMap
は、このミューテックスがserviceMap
を保護する目的で使用されることを示しています。 -
readRequestHeader
関数内のロック操作の変更:- server.mu.Lock() + server.mu.RLock() service = server.serviceMap[serviceMethod[0]] - server.mu.Unlock() + server.mu.RUnlock()
readRequestHeader
関数は、クライアントからのRPCリクエストヘッダーを読み取り、リクエストされたサービスをserviceMap
から検索する役割を担っています。- 以前は
server.mu.Lock()
とserver.mu.Unlock()
を使用して、serviceMap
へのアクセスを排他的にロックしていました。 - 変更後は
server.mu.RLock()
とserver.mu.RUnlock()
を使用するようになりました。これは、serviceMap
からサービスを検索する操作が読み取り操作であるため、複数のゴルーチンが同時に読み取りロックを取得できるようにするためです。これにより、サービス検索の並行性が向上し、RPCサーバーのスループットが改善されます。
- 以前は
この変更により、serviceMap
への読み取りアクセスは並行して行われるようになり、書き込みアクセス(サービスの登録など、このコミットでは直接示されていないが、Server
構造体の他のメソッドで mu.Lock()
が使用される)は引き続き排他的に保護されます。
関連リンク
- Go CL (Change List): https://golang.org/cl/6494044
参考にした情報源リンク
- Go言語
sync
パッケージのドキュメント: https://pkg.go.dev/sync - Go言語
net/rpc
パッケージのドキュメント: https://pkg.go.dev/net/rpc - Go言語における並行処理と同期プリミティブに関する一般的な情報源 (例: Go by Example - Mutexes, RWMutexes)