Skip to content

Instantly share code, notes, and snippets.

@robbiev
Last active January 21, 2023 18:27
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robbiev/1854d3fc9dcb7ae884423e39f93dc1e7 to your computer and use it in GitHub Desktop.
Save robbiev/1854d3fc9dcb7ae884423e39f93dc1e7 to your computer and use it in GitHub Desktop.
select with priority in go
// SOLUTION 1
// see https://groups.google.com/forum/#!topic/golang-nuts/ChPxr_h8kUM
func maybe(b bool, c chan int) chan int {
if !b {
return nil
}
return c
}
select {
case <-maybe(val>0, p):
val--
case <-v:
val++
}
// SOLUTION 2
// see https://groups.google.com/forum/#!topic/golang-nuts/M2xjN_yWBiQ
select {
case <-higher:
foo()
default:
select {
case <-lower:
bar()
default:
...
}
}
@kent-h
Copy link

kent-h commented Nov 26, 2019

If default is not defined on the innermost select, it should include all previous cases, like:

select  {
case <-higher:
        foo()
default:
        ...
        select {
        case <-higher:
                foo()
        case <-lower:
                bar()
        ...
        }
}

Thus higher is prioritized, but any channel can trigger execution if none are ready initially.

@muir
Copy link

muir commented Aug 20, 2022

Those are both incorrect, at least in the case where you don't have defaults.

for {
  select {
  case <- higher:
     processHigher()
  case <- lower:
     Lower:
     for {
       select {
       case <- higher:
           processHigher()
       default:
           break Lower
       }
    }
    processLower()
}

The above will process higher & lower forever, but will only process lower if the is no higher available.

@kent-h
Copy link

kent-h commented Aug 27, 2022

@muir Priority is only relevant when execution first hits this block.

If there is is data in higher, process it first. Then, wait for data from either higher or lower.
If there is no data in either channel, wait for any data from either channel, and process whichever arrives first.
Even if higher and lower are written at roughly the same time, go will typically be allowed to reorder the reads, such that your solution does not make any stronger ordering guarantees.

The only exceptional case where your solution would behave differently is when there's a known happens-before relationship between the writes to higher and lower, (guaranteeing that they occur in that order).
Very unusual to have events passed this way, & not something I'd want to rely on.

@muir
Copy link

muir commented Aug 27, 2022

@kent-h

I'm not sure we're talking about the same problem so let's make sure of that first. I think the problem is: In a stream of events that might have gaps where there are no events, process higher priority events before processing lower priority events. Do not spin the CPU when there are no events.

Neither your code nor @robbiev 's #2 meet that criteria.

The behavior of #2 is:

  1. If there is a higher available right now then process it.
  2. If not, and there is a lower available right now then process it.
  3. If not, then exit the code (loop, function, etc)

If we put this code in a loop, it would spin the CPU when there were no events to read.

The behavior of your code is:

  1. If there is a higher available right now then process it.
  2. If not, then wait for either a higher or a lower to arrive and process whichever arrives first or pick one randomly if they both arrive at once.
  3. Then exit the code (loop, function, etc)

If we put your code in a loop, then it would only give preference to higher if there were higher waiting in the queue. That is a much weaker guarantee that what my code provides.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment