The Applied Go Weekly Newsletter logo

The Applied Go Weekly Newsletter

Subscribe
Archives
February 16, 2025

You Cannot Simply Kill A Goroutine • The Applied Go Weekly Newsletter 2025-02-16

AppliedGoNewsletterHeader640.png

Your weekly source of Go news, tips, and projects

You cannot simply kill a goroutine

Hi ,

If you ever worked with system threads or thread libraries of various languages before, didn't you find the amount of ceremony around managing threads a bit exaggerated? Create, run, suspend, detach, resume,...

Or do goroutines feel odd due to the complete lack of "remote control"?

Especially, when you were new to Go, did it seem weird that goroutines cannot be stopped from the outside?

If you miss the power of thread control, this issue's Spotlight shows you how to reclaim some of it.

Keep go-ing!

– Christoph

Featured articles

We Replaced Our React Frontend with Go and WebAssembly - Dagger

Why would anyone write their frontend with Go and WebAssembly? Dagger displays operations via two real-time visualization interfaces (web and terminal), and wanted to unify the two interface codebases, which meant to kick out TypeScript and React and bring Go to the browser using the Go-App framework.

Practical OpenAPI in Go - by Alex Pliutau

From an API spec to server code, client code, and documentation.

Go 1.24.0 is released

Go 1.24 is now officially released. Have fun!

Podcast corner

go podcast() 050: Security, devops, testing in Go with Jakub Jarosz

Jakub Jarosz is writing a Go book: "Performance Tuning and Benchmarking". In this episode, he talks to Dominc St-Pierre about his transition from Python to Go, the growing importance of security in software development, the challenges of adapting legacy applications to modern cloud-native environments like Kubernetes, and the need for continuous testing and refactoring.

Cup o' Go: 💯 Go 1.24 release party - LIVE SHOW FOR EPISODE 100! 🎙️

Jonathan and Shay love to dance at two parties simultaneously. Or better, join two parties into one. In this episode, which was recorded live, they celebrate Go 1.24 and the 100th episode of Cup o' Go. Congratulations!

Spotlight: Killing me softly, or: how goroutines want to exit

If you have worked with POSIX threads in the past, or with threading libraries in various languages, you might have been surprised by the completely different approach to managing concurrency.

POSIX threads, or system threads, come with a big dashboard full of control knobs, or rather, a set of control functions such as pthread_atfork(3), pthread_cancel(3), pthread_cleanup_push(3), pthread_create(3), pthread_detach(3), pthread_equal(3), pthread_exit(3), pthread_kill(3), pthread_setcancelstate(3), pthread_setcanceltype(3), pthread_setspecific(3), pthread_sigmask(3), pthread_sigqueue(3), or pthread_testcancel(3).

Goroutines, on the other hand, have only one "knob":

The go directive.

No create, cancel, cancel states, detach, fork handling, thread variable creation magic, or kill mechanism.

Especially, no kill mechanism.

Which raises one or another eyebrow along with the question, "but how can I stop a goroutine from the outside?"

How to stop a goroutine from the outside

Short answer: you can't.

Long answer: You can't, and you shouldn't, and you can instead politely ask the goroutine to exit.

You can't, because there simply is no killswitch.

You shouldn't, as killing concurrently executing code from the outside can have unwanted side effects on open resources or other parts of the program that are in exchange with that concurrent code.

How to ask a goroutine to exit

However, you can politely ask the goroutine to exit, and you even have several easy ways to do that:

  1. By closing a channel that the goroutine is listening to.
  2. By sending a value to a channel that the goroutine is listening to.
  3. By passing a context.Context to the goroutine and calling context.CancelFunc from the outside.

Exiting through channel communication

The first two options are suitable for simple scenarios, especially if you have no Context to pass around.

Here is how to have a goroutine exit by closing a channel:

package main
import (
    "fmt"
    "time"
)
func main() {
    quit := make(chan struct{})
    go func() {
        for {
            select {
            case <-quit:
                fmt.Println("goroutine exiting")
                return
            default:
                fmt.Println("goroutine working")
                time.Sleep(1 * time.Second)
            }
        }
    }()
    time.Sleep(3 * time.Second)
    fmt.Println("asking to exit")
    close(quit)
    time.Sleep(1 * time.Second)
}

(Playground link)

The case <-quit case will be selected when the quit channel is closed, because once a channel is closed, the readers start recieving the zero value of the channel's type immediately.

Note that it's the goroutine's responsibility to exit when the request arrives. Within the <- quit case, the goroutine can do necessary cleanup such as closing resources or returning them to a resource pool.

The second option is similar, but instead of closing the channel, you send a value to it:

    quit <- struct{}{}

(Put this in place of close(quit) above.)

Exiting through context.Context

The third option is more flexible and is the standard way to exit goroutines that use a Context.

A quick refresher: context.Context is an interface that carries deadlines, cancellation signals, and request-scoped values across API boundaries and between processes.

To exit a goroutine using a Context, you create a WithCancel context that has a Done channel and provides a cancel function. By calling the cancel function, you signal the goroutine to exit.

Here's an example:

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go func() {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("goroutine exiting")
                return
            default:
                fmt.Println("goroutine working")
                time.Sleep(1 * time.Second)
            }
        }
    }()
    time.Sleep(3 * time.Second)
    fmt.Println("cancelling")
    cancel()
    time.Sleep(1 * time.Second)
}

(Playground link)

Goroutines are a simple concurrency mechanism

Goroutines are ideally suited for one-time concurrent work. They are extremely lightweight and have a low starting overhead. There is no need to "remote-control" goroutines to start, stop, pause, or resume them. If the work you want to implement concurrently is so complex that you need such mechanisms, forking or calling a new process is probably an approach worth considering. Or you simplify the concurrent work enough so that you won't need any sophisticated goroutine runtime management.

Quote of the Week: On writing security-critical code

The first rule of writing security-critical systems in C: Don't write security-critical systems in C.

–Me, after I came across this NASA list of 10 rules for safety-critical software development

More articles, videos, talks

OpenTelemetry: A Guide to Observability with Go | Blog | Luca Cavallin

Where to send your Go apps' logs, traces, and metrics to? Let OpenTelemetry do this for you.

Five More Features of Go 1 24 - YouTube

Featuring directory-limited filesystem access, improved finaliszers, TextAppender and BinaryAppender interfaces, GOAUTH Environment variable, and go/types iterator functions.

Go 1.24's omitzero is another one of the best additions to the ecosystem in years · Jamie Tanna | Software Engineer

In JSON, undefined variables are simply omitted. Go has no such thing as undefined variables, but sending the zero value instead is often not an option. In Go 1.24, the struct field tag omitzero excludes a field from the generated JSON if it has the zero value.

Go 1.24 Just RELEASED – Weak Pointers, Generic Aliases & More! (HUGE UPDATE) - YouTube

All the new and shiny things in Go 1.24.

Parallel Streaming Pattern in Go: How to Scan Large S3 or GCS Buckets Significantly Faster - Viktor Nikolaiev's blog

How to speed up traversing S3 buckets? If you know the bucket structure, you can traverse the buckets concurrently.

Projects

Libraries

GitHub - ConduitIO/evolviconf: Library for parsing versioned configurations

Versioned configration is more than just "I add my config files to Git." Applications can use versions config files to apply only the changes or roll back to a prior version.

GitHub - eaxis/levenshtein: An early-exit Levenshtein implementation aka Exitshtein

Levenshtein is an algorithm for measuring the difference between two strings. The early exit option allows comparing large texts more efficiently.

GitHub - HugoSmits86/nativewebp: Native webp encoder for Go

This webp encoder produces smaller files and is faster than Go's PNG encoder.

Tools and applications

GitHub - biswaone/go-paste-it: Create shareable snippets of text

What does a Pythonista do to get a feeling for Go? Write a full-stack app.

GitHub - Achno/gowall: A tool to convert a Wallpaper's color scheme / palette, image to pixel art, color palette extraction, image upsacling with Adversarial Networks and more image processing features

So your desktop wallpaper became boring or does not match your color theme? Gowall to the rescue.

GitHub - titpetric/etl: Convenience tooling to perform database interactions from CI, bash scripts and automation processes.

A database-agnostic CLI tool for creating and updating records in PostgreSQL, SQLite, or MySql databases.

GitHub - gost-dom/project-harmony: An example app for gost-dom

Project Harmony is an example app showcasing the use of gost-dom, a headless browser written in Go and JS.

GitHub - serversfordev/deploy

Deployment tooling for minimalists (and a "first OSS project" project).

Completely unrelated to Go

How to "see" Wifi signals

Bees can see ultraviolet, humans can't. But humans can extend their vision through tech. A system using an array of Wifi antennas can detect the direction of Wifi radiation and visualize it as it it was a light source. (Project web site here.)

Local LLM code assistant for VIM (Apple Silico ("M" processor series) only 😢)

Use the power of AI coding assistance with Vim but without sending your data to Evil Corp's LLMs. Slight disadvantage: VimLM requires a Mac with an M-series processor.

It’s a knowledge problem! Or is it?

Why do intelligent people eat junk food? Why do software developers check in bad code? It's not because they wouldn't know better...

Happy coding! ʕ◔ϖ◔ʔ

Questions or feedback? Drop me a line. I'd love to hear from you.

Best from Munich, Christoph

Not a subscriber yet?

If you read this newsletter issue online, or if someone forwarded the newsletter to you, subscribe for regular updates to get every new issue earlier than the online version, and more reliable than an occasional forwarding. 

Find the subscription form at the end of this page.

How I can help

If you're looking for more useful content around Go, here are some ways I can help you become a better Gopher (or a Gopher at all):

On AppliedGo.net, I blog about Go projects, algorithms and data structures in Go, and other fun stuff.

Or visit the AppliedGo.com blog and learn about language specifics, Go updates, and programming-related stuff. 

My AppliedGo YouTube channel hosts quick tip and crash course videos that help you get more productive and creative with Go.

Enroll in my Go course for developers that stands out for its intense use of animated graphics for explaining abstract concepts in an intuitive way. Numerous short and concise lectures allow you to schedule your learning flow as you like.

Check it out.


Christoph Berger IT Products and Services
Dachauer Straße 29
Bergkirchen
Germany

Don't miss what's next. Subscribe to The Applied Go Weekly Newsletter:
LinkedIn
Powered by Buttondown, the easiest way to start and grow your newsletter.