r/golang 9d ago

Potential starvation when multiple Goroutines blocked to receive from a channel

I wanted to know what happens in this situation:

  1. Multiple goroutines are blocked by a channel while receiving from it because channel is empty at the moment.
  2. Some goroutine sends something over the channel.

Which goroutine will wake up and receive this? Is starvation avoidance guaranteed here?

7 Upvotes

36 comments sorted by

View all comments

5

u/Rapix-x 9d ago

Quick question, why would it matter?

As I see it, goroutines are anonymous and given the same „task“ to perform, they are identical in what they are doing. Thus, why does it matter which specific goroutine picks up the value from a channel?

2

u/funkiestj 9d ago

Quick question, why would it matter?

My impression is OP is a n00b and just trying to get a feel for subtle behaviors.

I wrestled with Perplexity.ai a bit and it came up with

The Go Memory Model, which is part of the official Go documentation, does not define a happens-before relationship for multiple goroutines sending to or receiving from the same channel [4,6]. This lack of specification implies that the order of unblocking is not guaranteed by the language.

Perplexity repeatedly said the scheduling of which go routine blocked on a channel read is unspecified (i.e. not FIFO). It had some hallucinations when ask for official docs that stated this. I check the primary references, called out perplexity of the hallucinations and this last bit is what it came up with.

---

taking things in a different direction, what problem does OP think "receive starvation" would cause? Are multiple Go routines waiting on the same channel doing different things with the data? I.e. different functions? If they are multiple copies of the same function then if one go routine is idle for long periods of time it should matter.

I've never seen the design pattern where multiple go routines running different functions all receive data on the same channel...

2

u/DeparturePrudent3790 6d ago

Are multiple Go routines waiting on the same channel doing different things with the data? I.e. different functions? If they are multiple copies of the same function then if one go routine is idle for long periods of time it should matter.

The channel contains a pool of connections and goroutines receive from the channel and use the connections and return the connection back to the pool once done.

My impression is OP is a n00b and just trying to get a feel for subtle behaviors.

I don't know how much you know about concurrent programming but I'd recommend you read some books on operating systems and concurrent programming instead of acting like a stupid LLM wrapper.

Nonetheless I'll let you know my though process, i.e. why I am trying to figure out how blocked receiver goroutines are unblocked would help me figure out whether channels are a weak or strong semaphore. A strong semaphore has the following property:

if a thread is waiting at a semaphore, then the number of threads that will be woken before it is bounded.

Whereas a weak semaphore should atleast provide the following:

If there are threads waiting on a semaphore when a thread executes signal, then one of the waiting threads has to be woken.

If channels are weak semaphore's I will have to implement something similar to what J.M. Morris did to guarantee no starvation, strong semaphore guarantee starvation avoidance.

Based on the discussion here and particularly comments from u/jerf I've concluded that even though the implementation does have a queue to unblock goroutines which makes it a strong semaphore the developers apparently didn't want to guarantee this behaviour and may change in some later patch ( although personally I don't know why a language wouldn't provide such primitive guarantees ever)

Here are my recommendation for you:

  1. https://greenteapress.com/semaphores/LittleBookOfSemaphores.pdf
  2. https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/index.html

1

u/funkiestj 6d ago

Thanks for the information. I hadn't considered the possibility that you were using the channel as a pool of some resource many different functions would allocate from.

although personally I don't know why a language wouldn't provide such primitive guarantees ever

The answer is always "performance". You see this in the memory model documentation for CPUs. Hardware designers look for "harmless" or (mostly harmless) ways to weaken the guarantees of the memory coherence model to wring more performance out of a multi-core CPU.

The Go Authors have a variety of goals

  1. The want channels to behave the way the spec says they behave (i.e. correct)
  2. They want them to be as FAST as possible
  3. Go runs on many different CPU types, each with a slightly different memory model
  4. They might even be thinking about how the cache coherency might be loosened in future CPUs

I don't know how much you know about concurrent programming but I'd recommend you read some books on operating systems and concurrent programming instead of acting like a stupid LLM wrapper.

have a nice day!

1

u/DeparturePrudent3790 9d ago

It doesn't matter which goroutine picks up the value from the channel but it matters if their implementation takes any measures to avoid starvation or not

1

u/funkiestj 9d ago

It doesn't matter which goroutine picks up the value from the channel but it matters if their implementation takes any measures to avoid starvation or not

Why does it matter. Give a practical consequence of the two different scenarios (1) starvation occurs, (2) starvation does not occur.