If you've made it this far, you’ve got a handle on the basics. But let's be honest: in the real world, things happen all at once. Sensors trigger, background tasks process, and the network chirps—all simultaneously. If you try to do everything in a single line, your software will be about as responsive as a brick. Enter Concurrency.
Channels: The pipes of the plumbing system
Think of a channel as a secure, typed pipe between two parts of your program. One side sends data, the other receives it. It’s how we keep our tasks talking without shouting over each other.
fn main() -> int {
ch := make(chan<int>)
// Send 42 into the pipe
ch <- 42
// Pull it out on the other side
val := <-ch
print("Received: {val}")
return 0
}
The arrow <- tells you the story: ch <- 42 is data flowing into the channel; <-ch is data emerging from it. Simple, right?
Spawn: Setting things in motion
spawn is how we tell the system, "Hey, go do this other thing while I keep working here." It starts a concurrent task, freeing up your main loop to handle the important stuff.
fn worker(ch: chan<int>) {
// Do something that takes a while...
ch <- 99
}
fn main() -> int {
ch := make(chan<int>)
spawn worker(ch)
val := <-ch
print("Got: {val}")
return 0
}
A word of caution: If your main function returns before your spawned tasks finish, the process dies and takes your background work with it. Always make sure your main thread waits for the children to report home.
Shared Memory: The Mutex
Sometimes, you need two tasks to look at the same variable. If you don't coordinate, you'll end up with a race condition—and those are the "my code worked on my machine but failed in production" bugs that keep us all up at night. For shared state, we use a Mutex (Mutual Exclusion) to ensure only one person holds the key to the data at a time.
import "std/sync"
var counter: int = 0
fn incrementer(mu: *sync.Mutex) {
mu.Lock()
// Everything here is safe from interference
counter = counter + 1
mu.Unlock()
}
Exercises
Don't go off and start writing a high-frequency trading bot just yet—try these out:
- The Double Agent: Spawn two tasks that each send one value on the same channel, then receive both values in main.
- The Fibonacci Stream: Write a fib(n: int, out: chan<int>) function that sends the first n Fibonacci numbers down a channel.
- The Shutdown Signal: Use a channel to act as a "done" signal, so your main function knows exactly when the spawned worker has packed up and left.
Congratulations
You’ve made it through the gauntlet. You now know enough to be dangerous, which is the most exciting place to be. You've got the tools to build, break, and rebuild better systems.
For the deeper, darker corners of the language—the stuff that really makes things fly—take a look at the skink-advanced/ examples and the docs/language_manual.md. Now, go build something interesting.
Next
If you want to know further, let's dive into Templates and Services.