schedgroup: a timer-based goroutine concurrency primitive

By Matt Layher

Elevator Pitch

It is often desirable for tasks to execute as soon and fast as possible, but there are circumstances where it’s useful to delay the execution of a task. This talk will introduce the schedgroup package, which provides a sync.WaitGroup-like API built on top of goroutines, channels, and runtime timers.

Description

Go provides first-class support for concurrency primitives such as goroutines, channels, and select statements. In addition, the Go runtime provides excellent support for high-resolution timers. By combining these primitives, we can create powerful new ones that take advantage of Go’s concurrency and runtime timer facilities.

This talk will introduce a timer-based concurrency primitive called a “schedgroup”: short for “scheduler group”. The schedgroup.Group API mimics the familiar and simple sync.WaitGroup from Go’s standard library. The key feature of the schedgroup.Group type is its ability to efficiently delay or schedule the execution of goroutines according to user-specified time values.

While prototyping the schedgroup package, several different designs were explored, each with differing advantages and disadvantages:

  • a minimal implementation which creates a timer per goroutine for scheduling tasks
  • a timer-polling approach which checks for scheduled tasks in a queue at a fixed interval
  • an event-driven scheduler approach which uses a priority queue and signaling with channels

This talk will demonstrate the strengths and weaknesses of each prototype, building on top of each previous iteration until we reach the final design and implementation of the package as it stands today. During the presentation, attendees will learn about concepts related to Go concurrency and runtime timers, including:

  • blocking and non-blocking channel send/receive operations within a select statement
  • patterns for implementing context cancelation in concurrent systems
  • troubleshooting and prevention of goroutine deadlocks
  • efficient use of Go’s time.Timer APIs for tracking elapsed time during operations
  • implementing a time-based priority queue with Go’s container/heap package

Notes

This talk demonstrates a variety of uses of Go’s concurrency and timer primitives, and is intended primarily for intermediate and advanced Go users. The schedgroup package’s exported API is small and easy to grasp, so the majority of the talk will be focused on the design and implementation of the package’s internals.

When I was a new Go developer, I often struggled to understand how to make effective use of Go’s concurrency primitives: especially the behavior of channel operations within a select statement. The majority of the complexity within the schedgroup package involves coordination between goroutines using channels and select statements. I intend to thoroughly explain more advanced uses of the select statement, especially with regards to blocking and non-blocking (buffered channel or select with default case) channel operations.

During the presentation, I plan to cover the following topics in roughly this order:

  • introduce the schedgroup package API and its use cases
  • small examples of the exported API of the package
  • introduce the naive prototype which spins up a goroutine and timer for every task
  • explain the pros/cons of the naive prototype:
    • pros: simplicity
    • cons: unbounded timers, unbounded goroutines
  • introduce the timer-polling prototype which schedules tasks from a central goroutine
  • introduce use of container/heap for a priority queue of time.Time values, explain heap data structure basics
  • explain the pros/cons of the timer-polling prototype:
    • pros: single timer goroutine for coordination, worker goroutines only spawned when needed
    • cons: complexity, frequent timer-polling ticks wake up goroutines, causing CPU usage spikes and inefficient goroutine scheduling
  • demonstrate how the timer-polling prototype is actually worse than the naive implementation due to timer polling
    • Note that this is why we prototype and measure! Assumptions must be proven or disproven.
  • introduce the final approach: event-driven timer scheduler with a priority queue and signaling via channels
  • explain the pros/cons: of the event-driven scheduler design:
    • pros: single timer goroutine for coordination, no timers or unneeded goroutines running when no tasks are scheduled, context cancelation support
    • cons: complexity, potential for deadlocks
  • conclude with summary of lessons learned and links to relevant source code and GoDoc pages

I am a veteran developer with a great deal of prior experience building large, concurrent systems with Go. I have previously presented at numerous Go conferences, and am heavily involved with the open source community as well. I believe this material is relevant and interesting to the Go community as a whole and would love the opportunity to present it at [conference].

Thank you for your time, and for your consideration.