Introdução Quero discutir o tipo de e os casos de uso e quando usá-lo. sync.Cond O que é ? sync.Cond Na linguagem de programação Go, é um tipo definido no pacote que representa uma variável de condição. Variáveis de condição são primitivas de sincronização usadas para coordenar goroutines, permitindo-lhes esperar que uma condição específica se torne verdadeira antes de prosseguir. sync.Cond sync O tipo fornece uma maneira de criar e gerenciar variáveis de condição. Possui três métodos principais: sync.Cond : Este método faz com que a goroutine chamadora espere até que outra goroutine sinalize a variável de condição. Quando a goroutine chama , ela libera o bloqueio associado e suspende a execução até que outra goroutine chame ou na mesma variável . Wait() Wait() Signal() Broadcast() sync.Cond : Este método ativa uma goroutine aguardando a variável de condição. Se vários goroutines estiverem esperando, apenas um deles será despertado. A escolha de qual goroutine será despertada é arbitrária e não garantida. Signal() : Este método ativa todas as goroutines que aguardam a variável de condição. Quando é chamado, todas as goroutines em espera são despertadas e podem prosseguir. Broadcast() Broadcast() Observe que requer um associado para sincronizar o acesso à variável de condição. sync.Cond sync.Mutex Ao usar , você pode coordenar a execução de goroutines com base em condições específicas, permitindo uma programação simultânea mais controlada e sincronizada em Go. sync.Cond Casos de uso comuns é comumente usado em cenários onde as goroutines precisam coordenar e se comunicar entre si com base em condições específicas. Vamos considerar casos de uso comuns para . sync.Cond sync.Cond Sincronização de Goroutine pode ser usado para sincronizar a execução de vários goroutines. Por exemplo, você pode ter várias goroutines que devem aguardar a satisfação de uma condição específica antes de prosseguir. As goroutines em espera podem chamar , e a goroutine de sinalização pode chamar ou para ativar as goroutines em espera quando a condição for atendida. 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() } Neste exemplo, temos duas goroutines. A primeira goroutine espera por uma condição usando , enquanto a segunda goroutine sinaliza a condição usando . cond.Wait() cond.Signal() Quando o programa é executado, a primeira goroutine adquire o bloqueio e então chama . Como a condição ainda não foi atendida, a primeira goroutine libera o bloqueio e suspende sua execução. cond.Wait() Enquanto isso, a segunda goroutine dorme por cinco segundos, simulando algum trabalho. Ele adquire o bloqueio e depois chama . Ele ativa a goroutine em espera, que então adquire o bloqueio e executa. cond.Signal() O uso de garante que a primeira goroutine espere até que a segunda goroutine sinalize a condição, permitindo a sincronização e coordenação entre as duas goroutines. sync.Cond Problema Produtor-Consumidor pode ser útil na solução do , um problema clássico de sincronização que envolve dois tipos de processos, produtores e consumidores, que compartilham um buffer ou fila comum de tamanho fixo. As goroutines produtoras podem usar ou para notificar as goroutines consumidoras quando novos dados estiverem disponíveis para consumo. sync.Cond problema produtor-consumidor 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() } Neste exemplo, temos uma goroutine produtora que produz mensagens e as adiciona ao canal de mensagens e uma goroutine consumidora que consome mensagens. O canal de mensagens possui tamanho máximo definido por . MaxMessageChannelSize A goroutine produtora adiciona mensagens ao canal de mensagens e usa para notificar a goroutine consumidora quando novos dados estão disponíveis. Se o canal de mensagens estiver cheio, a goroutine produtora espera usando até que o consumidor consuma alguns dados e libere espaço no canal de mensagens. cond.Signal() cond.Wait() Da mesma forma, a goroutine consumidora consome mensagens do canal de mensagens e usa para notificar a goroutine produtora quando espaço fica disponível no canal de mensagens. Se estiver vazio, a goroutine do consumidor espera usando até que o produtor produza alguns dados e os adicione ao canal de mensagens. cond.Signal() cond.Wait() Aqui, permite a coordenação e sincronização entre as goroutines produtoras e consumidoras. Garante que o consumidor espere quando o canal de mensagens estiver vazio e o produtor espere quando estiver cheio, resolvendo assim o problema produtor-consumidor. sync.Cond Sincronização de recursos Suponha que várias goroutines precisem de acesso exclusivo a um recurso compartilhado. pode ser usado para coordenar o acesso. Por exemplo, um conjunto de goroutines de trabalho pode precisar esperar até que um determinado número de recursos fique disponível antes de poder iniciar o processamento. As goroutines podem aguardar a variável de condição usando e notificar sobre a liberação de recursos usando ou . 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() } Neste exemplo, temos várias goroutines de trabalho que precisam de acesso exclusivo a recursos limitados. As goroutines dos trabalhadores adquirem e liberam recursos usando para coordenar com outros trabalhadores. Se nenhum recurso estiver disponível, as goroutines de trabalho aguardam até que a outra goroutine libere o recurso. cond.Signal() using cond.Wait() Neste exemplo, permite a sincronização e a coordenação entre as goroutines de trabalho, garantindo que as goroutines de trabalho esperem quando não houver recursos disponíveis, sincronizando efetivamente o acesso aos recursos. sync.Cond Notificação de Evento pode ser usado para notificar goroutines sobre eventos ou alterações específicas no sistema. Por exemplo, você pode ter goroutines aguardando um evento específico. Quando o evento acontece, a goroutine de sinalização pode usar ou para ativar as goroutines em espera e permitir que elas lidem com o evento. 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() } Aqui, temos várias goroutines de trabalho que realizam trabalho e sincronizam em um ponto de barreira. As goroutines dos trabalhadores incrementam um contador e então esperam na barreira ou sinalizam a barreira usando e com base na contagem de trabalhadores que alcançam a barreira. cond.Wait() cond.Broadcast() Cada goroutine trabalhador executa algum trabalho e então adquire o bloqueio para incrementar a variável do contador. Se o trabalhador atual for o último a alcançar a barreira, ele transmite a condição da barreira usando para acordar todos os trabalhadores em espera. Caso contrário, espera na barreira usando para ser notificado pelo último trabalhador. cond.Broadcast() cond.Wait() A sincronização da barreira garante que todas as goroutines dos trabalhadores alcancem a barreira antes que qualquer uma delas prossiga além dela. Pode ser útil em cenários que exigem a sincronização da execução de várias goroutines em um ponto específico do fluxo de trabalho. Observe que a barreira é implementada usando um contador simples neste exemplo. No entanto, em cenários mais complexos, poderá ser necessário considerar mecanismos ou condições de sincronização adicionais para garantir a sincronização correta e evitar condições de corrida. Conclusão Concluindo, é um tipo útil na linguagem de programação Go que permite a sincronização e coordenação entre goroutines com base em condições específicas. Ele fornece uma maneira de criar e gerenciar variáveis de condição. Possui métodos para aguardar, sinalizar e transmitir condições. Usando `sync.Cond`, você pode escrever programas simultâneos mais controlados e sincronizados em Go. sync.Cond É importante observar que é apenas uma das primitivas de sincronização fornecidas pela biblioteca padrão Go e seu uso depende dos requisitos específicos do seu programa simultâneo. Em alguns casos, outras primitivas de sincronização como canais ou podem ser mais adequadas. sync.Cond sync.WaitGroup