Go之sync.Cond

sync.Cond

条件変数は相互排他ロックに基づいています。条件変数は、クリティカルセクションと共有リソースの保護には使用されません。共有リソースにアクセスするスレッドを調整するために使用されます。共有リソースの状態が変化すると、ミューテックスによってブロックされたスレッドに通知するために使用できます。

条件変数の初期化はミューテックスと不可分であり、そのメソッドの一部もミューテックスに基づいています。

条件変数によって提供される3つのメソッドがあります。

mutexロックの下で通知を待機します(wait)。

ミューテックスのロックを解除した後の単一の通知(シグナル)。

mutexがロック解除された後のブロードキャスト通知(broadcast)。

var mailbox uint8

var lock sync.RWMutex

sendCond := sync.NewCond(&lock)

recvCond := sync.NewCond(lock.RLocker())

変数mailboxはメールボックスを表し、タイプはuint8です。値が0の場合はメールボックスに情報がないことを意味し、値が1の場合はメールボックスに情報があることを意味します。lockはタイプsync.RWMutexの変数であり、読み取り/書き込みロックであり、メールボックスのロックと見なすこともできます。

さらに、このロックに基づいて、条件変数を表す2つの変数sendCondとrecvCondが作成されました。これらはすべてタイプ* sync.Condであり、sync.NewCond関数によっても初期化されます。

sync.Mutexタイプやsync.RWMutexタイプとは異なり、sync.Condタイプはそのままでは使用できません。sync.NewCond関数を使用してのみ、そのポインター値を作成できます。この関数には、タイプsync.Lockerのパラメーター値が必要です

条件変数はミューテックスロックに基づいており、動作するにはミューテックスロックでサポートされている必要があります。したがって、ここのパラメータ値は必須であり、条件変数メソッドの実装に参加します

sync.Lockerは実際にはインターフェースであり、その宣言には2つのメソッド定義、つまりLock()とUnlock()のみが含まれています。

sync.Mutex型とsync.RWMutex型には、どちらもLockメソッドとUnlockメソッドがありますが、すべてポインターメソッドです。したがって、これら2つのタイプのポインタータイプは、sync.Lockerインターフェイスの実装タイプです。

sendCond変数が初期化されると、ロック変数に基づくポインター値がsync.NewCond関数に渡されます。その理由は、ロック変数のLockメソッドとUnlockメソッドが書き込みロックのロックとロック解除にそれぞれ使用され、それらはsendCond変数の意味に対応しているためです。sendCondは、情報を配置するために特別に準備された条件変数で、メールボックスへの情報の配置は、共有リソースへの書き込み操作と見なすことができます

recvCond変数は、情報を取得するために特別に準備された条件変数を表します情報の取得にはメールボックスのステータスの変更も含まれますが、幸いにも、これを行うのは1人だけであり、条件変数と読み取りロックの組み合わせも確認する必要があります。したがって、ここでは当面の間、共有リソースの読み取り操作としてインテリジェンスを取得することを考えます。

したがって、条件変数recvCondを初期化するには、ロック変数に読み取りロックが必要であり、タイプがsync.Lockerである必要もあります。

ただし、ロック変数の読み取りロックをロックおよびロック解除するために使用されるメソッドは、RLockおよびRUnlockであり、sync.Lockerインターフェースで定義されたメソッドと一致しません。sync.RWMutexタイプのRLockerメソッドは、この要件を満たすことができます。sync.NewCond関数を呼び出すときに、呼び出し式lock.RLocker()の結果値を渡すだけでよいので、関数は要件を満たす条件変数を返すことができます。

なぜlock.RLocker()によって値がロック変数の読み取りロックで取得されるのですか?実際、この値が持つLockメソッドとUnlockメソッドは、ロック変数のRLockメソッドとRUnlockメソッドを内部的に呼び出します。つまり、最初の2つのメソッドは、後の2つのメソッドのエージェントにすぎません。

これで4つの変数ができました。1つはメールボックスを表すメールボックスで、もう1つはメールボックスのロックを表すロックです。青い帽子の子を表すsendCondと赤い帽子の子を表すrecvCondの2つもあります。

 

条件変数のWaitメソッドは何をしますか?

条件変数のWaitメソッドは、主に4つのことを行います。

1.呼び出しゴルーチン(つまり、現在のゴルーチン)を現在の条件変数の通知キューに追加します。

2.現在の条件変数が基づいているmutexをアンロックします。

3.現在のgoroutineを待機状態にして、通知が到着したときにウェイクアップするかどうかを決定します。この時点で、goroutineは、Waitメソッドを呼び出すコード行をブロックします。

4.通知が来て、ゴルーチンをウェイクアップすることを決定した場合、ウェイクアップした後、現在の条件変数に基づいてmutexを再度ロックします。それ以降、現在のgoroutineは引き続き次のコードを実行します。

条件変数のWaitメソッドは、現在のゴルーチンをブロックする前に、それに基づいているmutexをロック解除するため、Waitメソッドを呼び出す前に、最初にmutexをロックする必要があります。そうでない場合、Waitメソッドが呼び出されたときに発生します。回復不可能なパニック。

条件変数のWaitメソッドがこれを行う必要があるのはなぜですか?mutexがすでにロックされているときにWaitメソッドが現在のgoroutineをブロックすると、誰がロックを解除するか想像できますか?他のゴルーチン?

これは相互排除ロックの重要な原則に違反していることは言うまでもありません。つまり、ロックとロック解除のペアです。他のゴルーチンをロック解除できる場合でも、ロック解除が繰り返されるとどうなりますか?これにより引き起こされたパニックは回復できません。

現在のゴルーチンをロック解除できず、他のゴルーチンがロック解除されない場合、クリティカルセクションに入り、共有リソースの状態を変更するのは誰ですか?共有リソースの状態が変更されない限り、通知のために現在のゴルーチンが起こされた場合でも、Waitメソッドが再度実行され、再びブロックされます。

したがって、条件変数のWaitメソッドが最初にミューテックスをロック解除しない場合、2つの結果のみが発生します。パニックが原因で現在のプログラムがクラッシュするか、関連するゴルーチンが完全にブロックされます。

ifステートメントは共有リソースの状態を一度だけチェックしますが、forステートメントは状態が変化するまで複数のチェックを実行できます。なぜ複数の検査を行わなければならないのですか?

これは主に保険目的です。通知を受け取ってゴルーチンが目覚めたが、共有リソースの状態がまだ要件を満たしていない場合は、条件変数のWaitメソッドを再度呼び出し、次の通知を待ち続ける必要があります。

同じ状態の共有リソースを待機している複数のgoroutineがあります。たとえば、メールボックス変数の値が0でない場合、メールボックス変数の値はすべて0に変更されます。これは、複数の人がメールボックスに情報を置くのを待っているのと同じです。待機中のゴルーチンは複数ありますが、一度に成功できるゴルーチンは1つだけです。条件変数のWaitメソッドは、現在のゴルーチンがウェイクアップした後にミューテックスを再ロックします。成功したgoroutineが最終的にmutexのロックを解除すると、他のgoroutineが次々とクリティカルセクションに入りますが、共有リソースの状態がまだ望んでいるものではないことがわかります。このときforループが必要です。

共有リソースには2つの状態がない場合があります。たとえば、メールボックス変数の可能な値は、0と1だけでなく、2、3、4でもあります。この場合、状態が変化するたびに結果が1つしかないため、合理的な設計を前提として、1つの結果がgoroutineのすべての条件を満たしている必要はありません。これらの満たされていないgoroutineは、明らかに待機し続け、チェックする必要があります。

共有リソースの状態が2つしかない可能性があり、メインの問題で実装した例のように、各状態に関係するgoroutineは1つだけです。ただし、それでも、forステートメントを使用する必要があります。その理由は、複数のCPUコアを備えた一部のコンピューターシステムでは、条件変数が通知されなくても、Waitメソッドを呼び出すgoroutineが起こされる可能性があるためです。これは、オペレーティングシステム(Linuxなど)自体によって提供される条件変数でさえ、コンピューターハードウェアのレベルによって決定されます。

要約すると、条件変数をラップするWaitメソッドを使用する場合は、常にforステートメントを使用する必要があります

 

条件変数のシグナルメソッドとブロードキャストメソッドの類似点と相違点は何ですか?

通知の送信には、条件変数のシグナルメソッドとブロードキャストメソッドが使用されます。違いは、前者の通知はそれを待っているゴルーチンを起こすだけで、後者の通知はそれを待っているすべてのゴルーチンを起こすことです。

条件変数のWaitメソッドは常に現在のゴルーチンを通知キューの最後に追加し、そのSignalメソッドは常に通知キューの先頭から開始して、ウェイクアップできるゴルーチンを見つけます。そのため、Signalメソッドの通知により目覚めたゴルーチンは、一般的に最も早く待機するものです。

これら2つの方法の動作により、適用可能なシナリオが決まります。通知を待機しているゴルーチンが1つだけであるか、要件を満たすためにゴルーチンを起こしているだけの場合は、条件変数のSignalメソッドを使用してください。

それ以外の場合は、各ゴルーチンが予期する共有リソースのステータスを設定する限り、Broadcastメソッドを使用することが常に正しいです。

また、Waitメソッドとは異なり、mutexの保護下で条件変数のSignalメソッドやBroadcastメソッドを実行する必要はありません。逆に、条件変数の基になっているミューテックスをロック解除した後で、これらの2つのメソッドを呼び出す方が適切です。これにより、プログラムの運用効率が向上します。

条件変数の通知は即時に行われることに注意してくださいつまり、通知を送信するときにこれを待機するgoroutineがない場合、通知は直接破棄されます。この後に待機を開始するゴルーチンは、後続の通知によってのみ起こされます。

package sync

import (
	"sync/atomic"
	"unsafe"
)

// Cond implements a condition variable, a rendezvous point
// for goroutines waiting for or announcing the occurrence of an event.
// Each Cond has an associated Locker L (often a *Mutex or *RWMutex), which must be held when changing the condition and when calling the Wait method.

type Cond struct {
	noCopy noCopy

	// L is held while observing or changing the condition
	L Locker

	notify  notifyList
	checker copyChecker
}

// NewCond returns a new Cond with Locker l.
func NewCond(l Locker) *Cond {
	return &Cond{L: l}

func (c *Cond) Wait() {
	c.checker.check()
	t := runtime_notifyListAdd(&c.notify)
	c.L.Unlock()
	runtime_notifyListWait(&c.notify, t)
	c.L.Lock()
}

// Signal wakes one goroutine waiting on c, if there is any.
// It is allowed but not required for the caller to hold c.L during the call.
func (c *Cond) Signal() {
	c.checker.check()
	runtime_notifyListNotifyOne(&c.notify)
}

// Broadcast wakes all goroutines waiting on c.
// It is allowed but not required for the caller to hold c.L during the call.
func (c *Cond) Broadcast() {
	c.checker.check()
	runtime_notifyListNotifyAll(&c.notify)
}

// copyChecker holds back pointer to itself to detect object copying.
type copyChecker uintptr

func (c *copyChecker) check() {
	if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
		!atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
		uintptr(*c) != uintptr(unsafe.Pointer(c)) {
		panic("sync.Cond is copied")
	}
}
type noCopy struct{}

// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}

 

公開された127元の記事 ウォン称賛24 ビュー130 000 +

おすすめ

転載: blog.csdn.net/Linzhongyilisha/article/details/105471955