Go言語による並行処理プログラミング 相互排他ロック 詳細へ
1. ミューテックス ロック ミューテックス
1.1 Mutexの紹介
conda install cudatoolkit=9.0.
この言語の同期化ツールは主に
conda install cudnn=7
パッケージ、および相互排他的ロック (
conda install tensorflow-gpu=1.9
) と読み取り/書き込みロック (
RWMutex
) は
sync
メソッドを実装しています。
ミューテックスロックは、クリティカルゾーンを保護するために使用することができます。
goroutine
が同時にそのクリティカルゾーン内にある場合。これには主にロック(
Lock
メソッド)とアンロック(
Unlock
メソッド) を使用すると、最初の 2 つの操作は
goroutine
がロックされ、離れるとロックが解除される。
ミューテックスロック(Mutex)を使用する場合は、以下のことに注意してください。
-
繰り返しミューテックスをロックしないでください。ブロックされ、またデッドロックになる可能性があります(
deadlock
). - 相互排他的ロックを解除することで、重複ロックを回避する役割もあります。
- アンロックまたはアンロックされたミューテックスロックをアンロックしないようにすること。
-
複数の関数間でミューテックスロックを直接渡さないよう
sync.Mutex
型は値型であり、関数に渡すとそのコピーが作成され、関数内のロックに対する操作は元のロックに影響を与えません。
要するに、ミューテックスロックはクリティカルな領域を保護するためにのみ使用され、ロック後は忘れずにロックを解除し、各ロック操作に対して、対応するアンロック操作は1つだけでなければならず、つまり、ロックとアンロックは対になって行われなければならず、最も安全な方法は、以下のようになります。
defer
ステートメントを使用します。
1.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
は、子ゴルーチンの数だけ、最後にこのチャネルから要素を受け取る。メインゴルーチンはそれが終わると終了する。
このコードでは、並行プロセスを使って、口座への複数(5回)の入出金を実装しています。まず、ロックがない場合について見てみましょう。
<ブロッククオート
残高:1000
500回入金後の残高 1500
バランス:1000
500摂取後の残高:1000
バランス:1000
500回入金後の残高 1500
バランス:1000
500摂取後の残高:1000
バランス:1000
500回入金後の残高 1500
バランス:1000
500摂取後の残高:1000
バランス:1000
500摂取後の残高:500
バランス:1000
500回入金後の残高 1000
バランス:1000
500摂取後の残高:500
バランス:1000
500回入金後の残高 1000
現在の残高:1000
例えば、2回目に500を取って1000の残高が1000のままであったり、この同じリソースの奪い合いがRace Conditionとして現れたりと、混乱があることがお分かりになると思います。
ロック実行の結果がこちらです。
残高:1000
500摂取後のバランス:500バランス 500
500ドル入金後の残高:1000ドル残高:1000
500摂取後の残高:500バランス 500
500ドル入金後の残高:1000ドル残高:1000
500摂取後の残高:500バランス 500
500ドル入金後の残高:1000ドル残高:1000
500回入金後の残高 1500残高: 1500
500摂取後の残高:1000残高:1000
500摂取後の残高:500バランス 500
500ドル入金後の残高:1000ドル現在の残高:1000
ロック追加後は正常です。
以下は、ミューテックスロックのより詳細なバージョンである、リード/ライト・ミューテックスロックです。
RWMutex
.
2. リード/ライトロック RWMutex
2.1 RWMutexの紹介
リード/ライトミュテックスロック
RWMutex
共有リソースの読み取り操作と書き込み操作をそれぞれ保護するための読み取りロックと書き込みロックが含まれています。
sync.RWMutex
は、その
Lock
メソッドと
Unlock
メソッドは、それぞれ書き込みロックとロック解除に使用され、その
RLock
メソッドと
RUnlock
メソッドは、それぞれ読み取りロックとロック解除に使用されます。
相互排他的ロックで
Mutex
なぜ今でもリードロックとライトロックが必要なのでしょうか?多くの同時処理では、同時読み出しが大きな割合を占め、書き込みは比較的少ないからです。読み書きロックは同時読み出しを可能にし、サービス性能を提供することができるのです。読み書きロックには次のような特徴があります。
<テーブル 読み取りロックと書き込みロック リードロック 書き込みロック 読み取りロック はい いいえ 書き込みロック いいえ いいえ
言い換えれば
-
共有リソースが読み込みロックと書き込みロックで保護されている場合、他の
goroutine
は書き込み操作を行えません。つまり、読み書き操作は並行して行うことができない、つまり互いに排他的である。 -
リードロックで保護されている場合、複数のリード操作を同時に実行することができます。
リード/ライト・ロックを使用する場合、次の点にも注意が必要です。
- ロックされていない読み書きロックは解除しないでください。
- リードロックに対してライトロックでアンロックしない
- リードロックでライトロックを解除することはできません
2.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)
}
実装結果です。
残高:1000
500摂取後の残高:5001回操作した後の残高 500
1回操作後のバランス 500
1回操作後のバランス 500
1回操作後のバランス 500
1回操作後のバランス 500
バランス 500
500ドル入金後の残高:1000ドル残高:1000
500摂取後の残高:500バランス 500
500ドル入金後の残高:1000ドル残高:1000
500回入金後の残高 1500残高: 1500
500摂取後の残高:1000残高:1000
500摂取後の残高:500バランス 500
500ドル入金後の残高:1000ドル残高:1000
500摂取後の残高:500バランス 500
500ドル入金後の残高:1000ドル10回操作した後の残高。1000
read and writeロックとmutexロックの違いは、read and writeロックは共有リソースの読み取りと書き込み操作を分離し、より高度なアクセス制御を可能にすることです。
要約すると
リードライトロックもミューテックスロックの一種で、ミューテックスロックの拡張版である。 使用する際は、以下の点に注意が必要です。
- ロックを追加した後は必ずロックを解除する
- ロックとアンロックを繰り返さない
- 無施錠のロックを解除しない 相互に排他的なロックは渡さない
この記事Go言語並行プログラミング相互排他的ロックの詳細については、ここで導入され、より関連するGo言語並行プログラミング相互排他的ロックの内容は、スクリプトハウスの過去の記事を検索してくださいまたは次の関連記事を閲覧し続けるあなたは将来的に多くのスクリプトハウスをサポートして願っています!.
関連
最新
-
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 実装 サイバーパンク風ボタン