1. ホーム
  2. スクリプト・コラム
  3. ゴラン

Go言語による並行処理プログラミング 相互排他ロック 詳細へ

2022-02-14 13:10:53

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摂取後の残高:500

1回操作した後の残高 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言語並行プログラミング相互排他的ロックの内容は、スクリプトハウスの過去の記事を検索してくださいまたは次の関連記事を閲覧し続けるあなたは将来的に多くのスクリプトハウスをサポートして願っています!.