Reputation: 2952
I am making my first experiences in Go, and so far I really like the goroutine and channels construct. I am wondering if there is an idiomatic way to avoid deadlocks in bidirectional communication between multiple goroutines. Consider the following example. There are three goroutines: producer, worker and controller.
The producer produces integers. In reality this could be data coming from a network connection for example.
The worker receives data from the producer and does some operation on it. Then, the worker directs the modified data to the controller.
The controller sends a command to the worker in some cases. In the example this would happen if the received integer is bigger than 180.
The deadlock happens when the controller tries to send a command to the worker, while the worker tries to send an integer to the controller.
producerToWorker := make(chan int)
workerToController := make(chan int)
controllerToWorker := make(chan bool) // bool represents a command for this example
// Worker
go func() {
for {
select {
case i := <-producerToWorker:
// Do some processing and send to controller
workerToController <- (2 * i) + 1
case <-controllerToWorker:
// Would react to the command here
}
}
}()
// Controller
go func() {
for {
select {
case i := <-workerToController:
fmt.Println(i)
if i > 180 {
// Send a command to the worker
controllerToWorker <- true
}
}
}
}()
// Producer
for {
producerToWorker <- rand.Intn(100)
}
Example output:
163
175
95
113
1
189 // No deadlock
23
125
179
57
149
23
91
191 // No deadlock
133
95
175
177
181 // No deadlock
17
175
63
27
181 // Deadlock!
fatal error: all goroutines are asleep - deadlock!
Buffering the channels would make this deadlock more unlikely, but not solve it logically. I would like to avoid mutexes if possible. How do you handle such situations in Go?
Edit: To give a more real-world description: I came across this problem when I tried to implement a websocket client. The websocket client (worker) connects to an external service (producer) and receives messages from it (producerToWorker) and passes them to the controller (workerToController) to handle the received messages. The controller needs to react to the received messages, for example send a response or disconnect the client when an invalid message is received (controllerToWorker).
Upvotes: 2
Views: 436
Reputation: 42413
[Is there ]an idiomatic way to avoid deadlocks in bidirectional communication between multiple goroutines?
No. Nothing "idiomatic" or based on a "pattern".
How do you handle such situations in Go?
You redesign. Concurrent circular data flow is best avoided.
Upvotes: 1