導入 のタイプと使用例、そしていつ使用するかについて説明します。 sync.Cond とは何ですか? sync.Cond Go プログラミング言語では、 条件変数を表す パッケージで定義された型です。条件変数は、特定の条件が満たされるまで待機してから処理を続行できるようにすることで、goroutine を調整するために使用される同期プリミティブです。 sync.Cond sync 型は、条件変数を作成および管理する方法を提供します。主なメソッドは 3 つあります。 sync.Cond : このメソッドは、呼び出し元のゴルーチンを、別のゴルーチンが条件変数にシグナルを送るまで待機させます。ゴルーチンが を呼び出すと、関連付けられたロックが解放され、別のゴルーチンが同じ 変数で または を呼び出すまで実行が中断されます。 Wait() Wait() sync.Cond Signal() Broadcast() : このメソッドは、条件変数を待機している 1 つの goroutine を起動します。複数の goroutine が待機している場合は、そのうちの 1 つだけが起動されます。どの goroutine を起動するかは任意であり、保証されません。 Signal() : このメソッドは、条件変数を待機しているすべての goroutine を起動します。Broadcast が呼び出されると、待機中のすべての goroutine が起動され、続行できるようになります。 Broadcast() Broadcast() では、条件変数へのアクセスを同期するために、関連付けられた が必要であることに注意してください。 sync.Cond sync.Mutex を使用すると、特定の条件に基づいて goroutine の実行を調整できるため、Go でより制御され同期された並行プログラミングが可能になります。 sync.Cond 一般的な使用例 は、特定の条件に基づいて goroutine が相互に調整および通信する必要があるシナリオでよく使用されます の一般的な使用例を考えてみましょう。 sync.Cond sync.Cond Goroutine 同期 、複数のゴルーチンの実行を同期するために使用できます。たとえば、処理を進める前に特定の条件が満たされるのを待つ必要があるさまざまなゴルーチンがあるとします。待機中のゴルーチンは を呼び出し、シグナリング ゴルーチンは条件が満たされたときに または を呼び出して待機中のゴルーチンを起動できます。 sync.Cond cond.Wait() cond.Signal() cond.Broadcast() package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) wg.Add(2) go func() { fmt.Println("Goroutine 1 is started") defer wg.Done() cond.L.Lock() defer cond.L.Unlock() fmt.Println("Goroutine 1 is waiting for condition") cond.Wait() fmt.Println("Goroutine 1 met the condition") fmt.Println("Goroutine 1 is done") }() go func() { fmt.Println("Goroutine 2 is started") defer wg.Done() time.Sleep(5 * time.Second) // Simulating some work cond.L.Lock() defer cond.L.Unlock() fmt.Println("Goroutine 2 is signaling condition") cond.Signal() fmt.Println("Goroutine 2 completed signaling") fmt.Println("Goroutine 2 is done") }() wg.Wait() } この例では、2 つの goroutine があります。最初の goroutine は を使用して条件を待機し、2 番目の goroutine は を使用して条件を通知します。 cond.Wait() cond.Signal() プログラムが実行されると、最初のゴルーチンがロックを取得し、 を呼び出します。条件がまだ満たされていないため、最初のゴルーチンはロックを解放し、実行を中断します。 cond.Wait() 一方、2 番目のゴルーチンは 5 秒間スリープし、何らかの作業をシミュレートします。ロックを取得してから、 を呼び出します。待機中のゴルーチンを起動し、ロックを取得して実行します。 cond.Signal() を使用すると、最初の goroutine は 2 番目の goroutine が条件を通知するまで待機し、2 つの goroutine 間の同期と調整が可能になります。 sync.Cond 生産者・消費者問題 、プロデューサーとコンシューマーという 2 種類のプロセスが共通の固定サイズのバッファまたはキューを共有する、典型的な同期問題である を解決するのに役立ちます。プロデューサー ゴルーチンは、 または を使用して、新しいデータが使用可能になったときにコンシューマー ゴルーチンに通知できます。 sync.Cond プロデューサー-コンシューマー問題 cond.Signal() cond.Broadcast() package main import ( "fmt" "sync" "time" ) const MaxMessageChannelSize = 5 func main() { var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) messageChannel := NewMessageChannel(MaxMessageChannelSize) producer := NewProducer(cond, messageChannel) consumer := NewConsumer(cond, messageChannel) wg.Add(2) go func() { defer wg.Done() for i := range 10 { producer.Produce(fmt.Sprintf("Message %d", i)) } }() go func() { defer wg.Done() for range 10 { consumer.Consume() } }() wg.Wait() } type MessageChannel struct { maxBufferSize int buffer []string } func NewMessageChannel(size int) *MessageChannel { return &MessageChannel{ maxBufferSize: size, buffer: make([]string, 0, size), } } func (mc *MessageChannel) IsEmpty() bool { return len(mc.buffer) == 0 } func (mc *MessageChannel) IsFull() bool { return len(mc.buffer) == mc.maxBufferSize } func (mc *MessageChannel) Add(message string) { mc.buffer = append(mc.buffer, message) } func (mc *MessageChannel) Get() string { message := mc.buffer[0] mc.buffer = mc.buffer[1:] return message } type Producer struct { cond *sync.Cond messageChannel *MessageChannel } func NewProducer(cond *sync.Cond, messageChannel *MessageChannel) *Producer { return &Producer{ cond: cond, messageChannel: messageChannel, } } func (p *Producer) Produce(message string) { time.Sleep(500 * time.Millisecond) // Simulating some work p.cond.L.Lock() defer p.cond.L.Unlock() for p.messageChannel.IsFull() { fmt.Println("Producer is waiting because the message channel is full") p.cond.Wait() } p.messageChannel.Add(message) fmt.Println("Producer produced the message:", message) p.cond.Signal() } type Consumer struct { id int cond *sync.Cond messageChannel *MessageChannel } func NewConsumer(cond *sync.Cond, messageChannel *MessageChannel) *Consumer { return &Consumer{ cond: cond, messageChannel: messageChannel, } } func (c *Consumer) Consume() { time.Sleep(1 * time.Second) // Simulating some work c.cond.L.Lock() defer c.cond.L.Unlock() for c.messageChannel.IsEmpty() { fmt.Println("Consumer is waiting because the message channel is empty") c.cond.Wait() } message := c.messageChannel.Get() fmt.Println("Consumer consumed the message:", message) c.cond.Signal() } この例では、メッセージを生成してメッセージ チャネルに追加するプロデューサー ゴルーチンと、メッセージを消費するコンシューマー ゴルーチンがあります。メッセージ チャネルの最大サイズは、 によって定義されます。 MaxMessageChannelSize プロデューサー ゴルーチンはメッセージ チャネルにメッセージを追加し、新しいデータが利用可能になったときに を使用してコンシューマー ゴルーチンに通知します。メッセージ チャネルがいっぱいの場合、プロデューサー ゴルーチンは、コンシューマーがデータを消費してメッセージ チャネルのスペースを解放するまで を使用して待機します。 cond.Signal() cond.Wait() 同様に、コンシューマ ゴルーチンはメッセージ チャネルからメッセージを消費し、メッセージ チャネルに空きスペースができたときに を使用してプロデューサー ゴルーチンに通知します。空の場合、コンシューマ ゴルーチンは、プロデューサーがデータを生成してメッセージ チャネルに追加するまで を使用して待機します。 cond.Signal() cond.Wait() ここで、 、プロデューサーとコンシューマーのゴルーチン間の調整と同期を可能にします。これにより、メッセージ チャネルが空のときにコンシューマーが待機し、メッセージ チャネルがいっぱいのときにプロデューサーが待機することが保証され、プロデューサー - コンシューマー問題が解決されます。 sync.Cond リソースの同期 複数の goroutine が共有リソースへの排他的アクセスを必要とするとします。sync.Cond 使用すると、アクセスを調整できます。たとえば、ワーカー goroutine のプールは、処理を開始する前に、一定数のリソースが利用可能になるまで待機する必要がある場合があります。goroutine は を使用して条件変数を待機し、 または を使用してリソースの解放を通知できます。 sync.Cond cond.Wait() cond.Signal() cond.Broadcast() package main import ( "fmt" "sync" "time" ) const MaxResources = 3 func main() { var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) resourceProvider := NewResourceProvider(cond, MaxResources) wg.Add(10) for i := range 10 { go func(workerID int) { defer wg.Done() worker := NewWorker(workerID, cond, resourceProvider) worker.Run() }(i) } wg.Wait() } type ResourceProvider struct { maxResources int availableResources int cond *sync.Cond } func NewResourceProvider(cond *sync.Cond, maxResources int) *ResourceProvider { return &ResourceProvider{ cond: cond, availableResources: maxResources, } } func (rp *ResourceProvider) AvailableResources() int { return rp.availableResources } func (rp *ResourceProvider) AcquireResoirce() { rp.availableResources-- } func (rp *ResourceProvider) ReleaseResource() { rp.availableResources++ } type Worker struct { id int cond *sync.Cond rp *ResourceProvider } func NewWorker(workerID int, cond *sync.Cond, rp *ResourceProvider) *Worker { return &Worker{ id: workerID, cond: cond, rp: rp, } } func (w *Worker) Run() { w.cond.L.Lock() for w.rp.AvailableResources() == 0 { fmt.Printf("Worker %d is waiting for resources\n", w.id) w.cond.Wait() } w.rp.AcquireResoirce() fmt.Printf("Worker %d acquired resource. Remaining resources: %d\n", w.id, w.rp.AvailableResources()) w.cond.L.Unlock() time.Sleep(1 * time.Second) // Simulating work w.cond.L.Lock() defer w.cond.L.Unlock() w.rp.ReleaseResource() fmt.Printf("Worker %d released resource. Remaining resources: %d\n", w.id, w.rp.AvailableResources()) w.cond.Signal() } この例では、限られたリソースへの排他的アクセスを必要とするワーカー ゴルーチンが複数あります。ワーカー ゴルーチンは、他のワーカーと調整するために を使用してリソースを取得および解放します。利用可能なリソースがない場合、ワーカー ゴルーチンは、他のゴルーチンがリソースを解放するまで 待機します。 cond.Signal() using cond.Wait() この例では、 によってワーカー goroutine 間の同期と調整が可能になり、リソースが利用できない場合にワーカー goroutine が待機することが保証され、リソース アクセスが効果的に同期されます。 sync.Cond イベント通知 、システム内の特定のイベントや変更について goroutine に通知するために使用できます。たとえば、特定のイベントを待機する goroutine を作成できます。イベントが発生すると、シグナル goroutine は または を使用して待機中の goroutine を起動し、イベントを処理できるようにします。 sync.Cond cond.Signal() cond.Broadcast() package main import ( "fmt" "sync" "time" ) const maxWorkersCount = 10 func main() { var counter int32 var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) wg.Add(maxWorkersCount) for i := range maxWorkersCount { go func(workerID int) { defer wg.Done() fmt.Printf("Worker %d performing work\n", workerID) time.Sleep(1 * time.Second) // Simulate work cond.L.Lock() defer cond.L.Unlock() counter++ if counter == maxWorkersCount { fmt.Println("All workers have reached the barrier") cond.Broadcast() } else { fmt.Printf("Worker %d is waiting at the barrier\n", workerID) cond.Wait() } fmt.Printf("Worker %d passed the barrier\n", workerID) }(i) } wg.Wait() } ここでは、バリア ポイントで作業を実行し、同期する複数のワーカー ゴルーチンがあります。ワーカー ゴルーチンはカウンターを増分し、バリアに到達したワーカーの数に基づいて、バリアで待機するか、 と を使用してバリアに信号を送ります。 cond.Wait() cond.Broadcast() 各ワーカー ゴルーチンは、何らかの作業を実行し、ロックを取得してカウンター変数を増分します。現在のワーカーがバリアに到達した最後のワーカーである場合、 を使用してバリア条件をブロードキャストし、待機中のすべてのワーカーを起動します。そうでない場合は、最後のワーカーからの通知を受け取るために を使用してバリアで待機します。 cond.Broadcast() cond.Wait() バリア同期により、すべてのワーカー ゴルーチンがバリアに到達してから、いずれかがバリアを越えることが保証されます。これは、ワークフローの特定の時点で複数のゴルーチンの実行を同期する必要があるシナリオで役立ちます。 この例では、バリアは単純なカウンターを使用して実装されていることに注意してください。ただし、より複雑なシナリオでは、正しい同期を保証し、競合状態を回避するために、追加の同期メカニズムまたは条件を考慮する必要がある場合があります。 結論 結論として、 Go プログラミング言語の便利な型であり、特定の条件に基づいて goroutine 間の同期と調整を可能にします。条件変数を作成および管理する方法を提供します。条件を待機、シグナル、ブロードキャストするメソッドがあります。`sync.Cond` を使用すると、Go でより制御され同期された並行プログラムを作成できます。 sync.Cond Go 標準ライブラリによって提供される同期プリミティブの 1 つにすぎず、その使用法は並行プログラムの特定の要件によって異なることに注意してください。場合によっては、チャネルや などの他の同期プリミティブの方が適している場合があります。 sync.Cond sync.WaitGroup