Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 パッケージは、プリミティブな同期メカニズムを提供し、並行処理におけるデータ競合を防ぐために使用されます。

  1. sync.Mutex:

    • Mutex は「相互排他ロック」を意味します。
    • Lock() メソッドでロックを取得し、Unlock() メソッドでロックを解放します。
    • 一度に1つのゴルーチンのみがロックを保持できます。
    • ロックが取得されている間は、他のすべてのゴルーチンは Lock() を呼び出すとブロックされ、ロックが解放されるまで待機します。
    • 読み取りと書き込みの両方の操作に対して排他制御が必要な場合にシンプルで効果的ですが、読み取り操作が多い場合には並行性を損なう可能性があります。
  2. 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 に変更することで、以下の利点が得られます。

  1. 読み取り操作の並行性向上: readRequestHeader 関数内で serviceMap を検索する際に server.mu.RLock()server.mu.RUnlock() が使用されるようになりました。これにより、複数のゴルーチンが同時に serviceMap を読み取ることが可能になります。これは、RPCサーバーの主要なボトルネックの一つを解消し、特に読み取り負荷が高いシナリオでのスループットを大幅に向上させます。
  2. 書き込み操作の排他性維持: サービスの登録や削除といった 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

コアとなるコードの解説

  1. 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 を保護する目的で使用されることを示しています。

  2. 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言語 sync パッケージのドキュメント: https://pkg.go.dev/sync
  • Go言語 net/rpc パッケージのドキュメント: https://pkg.go.dev/net/rpc
  • Go言語における並行処理と同期プリミティブに関する一般的な情報源 (例: Go by Example - Mutexes, RWMutexes)