Go言語並行プログラミングのための相互排除ロックMutexと読み取り/書き込みロックRWMutex
並行プログラミングでは、複数の
Goroutine
Go言語には多くの同期ツールがありますが、今回は相互排他ロックについて紹介します
Mut
ex と読み取り/書き込みロック
RWMutex
を使用することができます。
I. 相互排他ロック Mutex
1. ミューテックス入門
Go言語の同期ツールは、主に以下のもので構成されています。
sync
パッケージ、および相互排他的ロック (
Mutex
) と読み取り/書き込みロック (
RWMutex
) は、sync パッケージに含まれるメソッドです。
ミューテックスロックは、クリティカルゾーンを保護するために使用することができます。
goroutine
が同時にクリティカルゾーンに存在することになります。主な操作はロック(Lockメソッド)とアンロック(Unlockメソッド)の2つで、最初に
goroutine
クリティカルゾーンに入るとロックされ、出るとアンロックされる。
ミューテックスロック(Mutex)を使用する場合は、以下のことに注意してください。
-
繰り返しミューテックスをロックしないでください。ブロックされ、またデッドロックになる可能性があります(
deadlock
). - 相互排他的ロックを解除することで、重複ロックを回避する役割もあります。
- アンロックまたはアンロックされたミューテックスロックをアンロックしないようにすること。
-
複数の関数間でミューテックスロックを直接渡さないよう
sync.Mutex
型は値型であり、関数に渡すとそのコピーが作成され、関数内のロックに対する操作は元のロックに影響を与えません。
要するに、ミューテックスロックはクリティカルな領域を保護するためにのみ使用され、ロック後は忘れずにロックを解除し、各ロック操作に対して、対応するアンロック操作は1つだけでなければならず、つまり、ロックとアンロックは対で行われなければならず、最も安全な方法は、以下の通りです。
defer
ステートメントを使用してロックを解除します。
2. Mutexの使用例
次のコードは、出金と入金操作をシミュレートしたものです。
package main
import (
"flag"
"fmt"
"sync"
)
var (
mutex sync.Mutex
balance int
protecting uint // whether to lock or not
sign = make(chan struct{}, 10) // channel to wait for all goroutines
)
// deposit money
func deposit(value int) {
defer func() {
sign <- struct{}{}
}()
if protecting == 1 {
mutex.Lock()
defer mutex.Unlock()
}
fmt.Printf("Balance: %d\n", balance)
balance += value
fmt.Printf("Balance after saving %d: %d\n", value, balance)
fmt.Println()
}
// Withdraw money
func withdraw(value int) {
defer func() {
sign <- struct{}{}
}()
if protecting == 1 {
mutex.Lock()
defer mutex.Unlock()
}
fmt.Printf("Balance: %d\n", balance)
balance -= value
fmt.Printf("Balance after taking %d: %d\n", value, balance)
fmt.Println()
}
func main() {
for i:=0; i < 5; i++ {
go withdraw(500) // take 500
go deposit(500) // deposit 500
}
for i := 0; i < 10; i++ {
<-sign
}
fmt.Printf("Current balance: %d\n", balance)
}
func init() {
balance = 1000 // initial account balance is 1000
flag.UintVar(&protecting, "protecting", 0, "whether to lock, 0 means no lock, 1 means lock")
}
上記のコードでは、チャンネルはメインの
goroutine
を待機させ、他の
goroutine
の実行が終了すると、各サブ
goroutine
は実行の終了前にチャンネルに要素を送信し、メインの
goroutine
は、最後にこのチャネルから要素を受け取ります。
goroutine
が同じ回数だけ表示されます。受信後、メインの
goroutine
.
このコードでは、並行スレッドを使用して、口座への複数(5)の入出金を実装しています。 まず、ロックがない場合を見てみましょう。
Balance: 1000
Balance after 500 deposits: 1500
Balance: 1000
Balance after 500 withdrawals: 1000
Balance: 1000
Balance after 500 deposits: 1500
Balance: 1000
Balance after 500 withdrawals: 1000
Balance: 1000
Balance after 500 deposits: 1500
Balance: 1000
Balance after 500 withdrawals: 1000
Balance: 1000
Balance after 500 withdrawals: 500
Balance: 1000
Balance after 500 deposits: 1000
Balance: 1000
Balance after 500 withdrawals: 500
Balance: 1000
Balance after 500 deposit: 1000
Current balance: 1000
例えば2回目に1000の残高が500を取った後も1000になっているなど、混乱があることがわかりますが、この同じリソースの奪い合いがレースコンディションとして現れるのです(
Race Condition
).
ロックの実装結果はこちらです。
Balance: 1000
Balance after taking 500: 500
Balance: 500
Balance after 500 deposit: 1000
Balance: 1000
Balance after 500 withdrawals: 500
Balance: 500
Balance after 500 deposits: 1000
Balance: 1000
Balance after 500 withdrawals: 500
Balance: 500
Balance after 500 deposits: 1000
Balance: 1000
Balance after 500 withdrawals: 1500
Balance: 1500
Balance after 500 withdrawals: 1000
Balance: 1000
Balance after 500 withdrawals: 500
Balance: 500
Balance after 500 deposits: 1000
Current balance: 1000
ロックを追加した後は正常に動作します。
より詳細な相互排他的ロックについては、以下で説明します。 リード/ライト・ミュテックス・ロック RWMutex。
II. リード/ライトロック RWMutex
1. RWMutexの紹介
リード/ライト・ミュテックス・ロック
RWMutex
共有リソースの読み取り操作と書き込み操作をそれぞれ保護するための読み取りロックと書き込みロックが含まれています。
sync.RWMutex
タイプのLockメソッド
Unlock
メソッドは、それぞれ書き込みロックとロック解除に使用され、その
RLock
メソッドと
RUnlock
メソッドは、それぞれ読み取りロックとロック解除に使用されます。
MutexロックMutexがあるのに、なぜリードロックとライトロックが必要なのでしょうか?なぜなら、多くの同時処理において、同時読み出しが大きな割合を占め、書き込みが比較的少ない場合、読み取りロックと書き込みロックによって同時読み出しが可能になり、サービスパフォーマンスが得られるからです。 リード・ライト・ロックには、次のような特徴があります。
<テーブル 読み取りロックと書き込みロック リードロック 書き込みロック 読み取りロック はい いいえ 書き込みロック いいえ いいえ
つまり
-
共有リソースが読み込みロックと書き込みロックで保護されている場合、他の
goroutine
は書き込み操作を行えません。つまり、読み書き操作は並行して行うことができない、つまり互いに排他的である。 -
リードロックで保護されている場合、複数のリード操作を同時に実行することができます。
リード/ライト・ロックを使用する場合、次の点にも注意が必要です。
- ロックされていない読み書きロックは解除しないでください。
- リードロックに対してライトロックでアンロックしない
- リードロックでライトロックを解除することはできません
2. RWMutexの使用例
これまでの出金・入金操作を書き換え、残高を照会する方法を追加します。
package main
import (
"fmt"
"sync"
)
// account represents a counter.
type account struct {
num uint // number of operations
balance int // balance
rwMu *sync.RWMutex // read/write lock
RWMutex // Read and write locks }
var sign = make(chan struct{}, 15) // channel to wait for all goroutines
// Check the balance: use read locks
func (c *account) check() {
defer func() {
sign <- struct{}{}
}()
c.rwMu.RLock()
defer c.rwMu.RUnlock()
fmt.Printf("Balance after %d operations: %d\n", c.num, c.balance)
}
// Deposit money: write lock
func (c *account) deposit(value int) {
defer func() {
sign <- struct{}{}
}()
c.rwMu.Lock()
defer c.rwMu.Unlock()
fmt.Printf("Balance: %d\n", c.balance)
c.num += 1
c.balance += value
fmt.Printf("Balance after saving %d: %d\n", value, c.balance)
fmt.Println()
}
// Take the money: write the lock
func (c *account) withdraw(value int) {
defer func() {
sign <- struct{}{}
}()
c.rwMu.Lock()
defer c.rwMu.Unlock()
fmt.Printf("Balance: %d\n", c.balance)
c.num += 1
c.balance -= value
fmt.Printf("Balance after taking %d: %d\n", value, c.balance)
fmt.Println()
}
func main() {
c := account{0, 1000, new(sync.RWMutex)}
for i:= 0; i < 5; i++ {
go c.withdraw(500) // take 500
go c.deposit(500) // save 500
go c.check()
}
for i := 0; i < 15; i++ {
<-sign
}
fmt.Printf("Balance after %d operations: %d\n", c.num, c.balance)
}
実装結果です。
Balance: 1000
Balance after 500: 500
Balance after 1 operation: 500
Balance after 1 operation: 500
Balance after 1 operation: 500
Balance after 1 operation: 500
Balance after 1 operation: 500
Balance: 500
Balance after 500 deposits: 1000
Balance: 1000
Balance after 500 withdrawals: 500
Balance: 500
Balance after 500 deposits: 1000
Balance: 1000
Balance after 500 withdrawals: 1500
Balance: 1500
Balance after 500 withdrawals: 1000
Balance: 1000
Balance after 500 withdrawals: 500
Balance: 500
Balance after 500 deposits: 1000
Balance: 1000
Balance after 500 withdrawals: 500
Balance: 500
Balance after 500 deposits: 1000
Balance after 10 operations: 1000
read and writeロックとmutexロックの違いは、read and writeロックは共有リソースに対する読み取りと書き込みの操作を分離し、より高度なアクセス制御を可能にすることです。
要約すると
リードライトロックもミューテックスロックの一種で、ミューテックスロックを拡張したものです。 使用する際は、以下の点に注意が必要です。
- ロックを追加した後は必ずロックを解除する
- ロックとアンロックを繰り返さない
- 無施錠のロックを解除しない 相互に排他的なロックは渡さない
Go言語並行プログラミングMutexとRWMutexの記事はこれだけです。Go言語のMutexとRWMutexについては、過去の記事を検索するか、以下の記事を引き続き閲覧してください。
関連
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Go言語の基本 goインストールコマンドの使用例 詳細
-
囲碁言語の基本的な列挙の使い方と例
-
Go言語基本デザインパターン - 戦略パターン例解説
-
Golangの高性能な永続化ソリューションBoltDB Databaseの紹介
-
golang ソートによるプログラミング開発 詳細
-
golangはファイルをダウンロードするためにマルチプロセッシングを実装しています(ブレークポイント転送をサポート)。
-
Golang開発ライブラリ集と役割説明
-
golang はシンプルなウェブソケット・チャットルーム機能を実装しています。
-
Go サービスでリンクトレースを行う方法を説明します。
-
囲碁言語におけるCGOの実践的な使用