Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Sicherheitslücken (CVE)USN-8819-3: Linux kernel vulnerabilities(29.09.2026 um 11:19 Uhr)
••••••••••
Sicherheitslücken (CVE)USN-8819-3: Linux kernel vulnerabilities(29.09.2026 um 11:19 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

A Developer’s Guide to sync.Once: Your Go Concurrency Lifesaver

1. Hey, Let’s Talk About sync.Once If you’ve ever written Go code with goroutines flying everywhere, you’ve probably hit that moment: “Wait, how do I make sure this only runs once?” Maybe it’s loading a config file, setting up a database …

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!




1. Hey, Let’s Talk About sync.Once



If you’ve ever written Go code with goroutines flying everywhere, you’ve probably hit that moment: “Wait, how do I make sure this only runs once?” Maybe it’s loading a config file, setting up a database connection, or initializing a singleton. Without some control, you’re risking duplicate work, wasted resources, or—worse—a crash from a race condition. Enter sync.Once, Go’s unsung hero from the sync package. It’s like a bouncer at a club: only one goroutine gets in to do the job, and everyone else just chills.



I’ve been coding in Go for over a decade, building everything from microservices to AI backends, and sync.Once has saved my bacon more times than I can count. It’s simple, lightweight, and does exactly what it promises: runs a piece of code once, no matter how chaotic your concurrency gets. In this guide, I’ll walk you through what it is, how it works, and how to use it like a pro—based on real-world wins and facepalms I’ve collected along the way.



This is for you if you’ve got a year or two of Go under your belt and know your way around goroutines and mutexes. We’ll go from the basics to the nitty-gritty, with code snippets, war stories, and tips to keep you out of trouble. Ready? Let’s dive in.






2. What’s sync.Once All About?






2.1 The Big Idea



sync.Once is your “do it once and done” tool. Imagine a bunch of goroutines all trying to load the same config file at startup. Without coordination, you’d get multiple reads, wasted CPU, or even corrupted data. sync.Once steps in and says, “First one in does the work; everyone else waits and grabs the result.” It’s perfect for:





  • Singletons: Think global loggers or configs.


  • Lazy loading: Like a database pool you only need when the first request hits.


  • Heavy setup: Say, loading a machine learning model without frying your RAM.



It’s not fancy, but it’s elegant as heck—and that’s why I love it.






2.2 How to Use It



The API is dead simple. You’ve got one method:




func (o *Once) Do(f func())








  • f: A function with no inputs or outputs.


  • What it does: Runs f the first time Do is called; skips it every time after.



Check this out:




package main

import (
"fmt"
"sync"
)

func main() {
var once sync.Once
f := func() {
fmt.Println("I’m only running once, promise!")
}
for i := 0; i < 5; i++ {
go once.Do(f) // 5 goroutines, 1 execution
}
fmt.Scanln() // Wait to see the output
}






Output:




I’m only running once, promise!






Five goroutines, one print. That’s sync.Once in action—clean and drama-free.






2.3 How’s It Different?



Let’s stack it up against the competition:





  • sync.Mutex: Locks a block of code so it can run multiple times safely. More flexible, more overhead.


  • init(): Runs once per package at startup, but you can’t control when. Static and rigid.


  • sync.Once: Runs once at runtime, when you say so. Goldilocks-level “just right.”



Think of sync.Mutex as a reusable lockbox, init() as a one-time setup script, and sync.Once as a “do it once, on demand” button. For initialization tasks, it’s usually the winner.






What’s Next?



You’ve got the gist: sync.Once is a concurrency cheat code for one-time jobs. But how does it pull off this “once and only once” trick under the hood? Let’s pop the hood and peek at the source code in the next section. Spoiler: it’s clever, but not complicated.






3. How sync.Once Works Its Magic



So, sync.Once promises to run your code exactly once, even with goroutines swarming like Black Friday shoppers. How does it pull that off? Is it some dark magic or just smart engineering? Let’s dig into the Go source code and break it down. Spoiler: it’s a masterclass in balancing simplicity, safety, and speed. By the end, you’ll see why it’s so reliable—and maybe pick up a concurrency trick or two.






3.1 Peeking Under the Hood






The Tiny Struct That Could



Here’s what sync.Once looks like inside (straight from Go 1.22):




type Once struct {
done uint32 // 0 = "not yet," 1 = "donezo"
m Mutex // The bouncer guarding the door
}








  • done: A little flag—0 means “nobody’s run it yet,” 1 means “we’re good.”


  • m: A sync.Mutex to keep things orderly when the first goroutine steps up.



It’s lean and mean—just two fields to rule them all.






The Do Method: Where It Happens



Here’s the Do method, simplified for clarity:




func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 1 { // Quick peek: done already?
return
}
o.m.Lock() // First in locks the door
defer o.m.Unlock() // Cleanup crew
if o.done == 0 { // Double-check under lock
defer atomic.StoreUint32(&o.done, 1) // Mark it done when finished
f() // Party time!
}
}






How It Flows:





  1. Fast Check: atomic.LoadUint32(&o.done) peeks at done without locking. If it’s 1, everyone bails early—no fuss.


  2. Lock Up: If done is 0, the first goroutine grabs the mutex. Others wait in line.


  3. Double-Check: Inside the lock, it confirms done is still 0 (safety first!).


  4. Run It: The lucky goroutine runs f().


  5. Flag It: After f() finishes, done flips to 1 atomically, and the lock opens.



It’s like a VIP list: the first goroutine gets in, flips the sign to “closed,” and everyone else just nods and moves on.






Picture This






Goroutine 1    Goroutine 2    Goroutine 3
| | |
Check: 0 Check: 0 Check: 0
| | |
Lock & run Wait Wait
| | |
f() runs Wait Wait
done = 1 | |
Unlock Check: 1 Check: 1
Return Return






The first one does the heavy lifting; the rest get a free pass.






3.2 The Secret Sauce






Atomic Ops + Locks = Win



sync.Once is a hybrid beast:





  • Atomic Reads: atomic.LoadUint32 is lightning-fast for checking done. No lock, no overhead—later calls fly through.


  • Mutex: The lock kicks in only for the first call, keeping the critical section safe.



It’s like a toll booth: the first car pays, then the gate stays open for everyone else.






Why Not Go Full Atomic?



You might wonder, “Why not use CompareAndSwap (CAS) instead of a mutex?” CAS is lock-free, sure, but it’s a retry loop under contention—think spinning your wheels in traffic. sync.Once picks a mutex for the first run because it’s simpler and predictable. After that, atomic reads take over, and it’s smooth sailing.






3.3 Is It Fast? Is It Safe?





  • Safety: Between atomic ops and the mutex, it’s rock-solid. No goroutine can sneak past the “done” flag.


  • Speed:


    • First call: A little lock overhead, but still O(1).

    • After that: Just an atomic read—basically free.








In a real project, I used sync.Once to init a cache. The first hit took a tiny lock blip (0.1% of runtime), but later accesses were as fast as a variable lookup. That’s the kind of efficiency I live for.






What’s Next?



Now you know the gears behind sync.Once. It’s not magic—just clever design with atomic ops and a mutex playing tag team. So, how do you take this and run with it in your own code? In the next section, I’ll share some real-world use cases from my toolbox, complete with code and stories from the trenches.






4. Where sync.Once Saves the Day



Okay, we’ve cracked open sync.Once and seen its guts—pretty slick, right? But the real fun starts when you put it to work. Over the years, I’ve leaned on it for everything from tiny config setups to monster resource loads in production. In this section, I’ll walk you through three killer use cases with code you can steal, plus some battle scars from my own projects. Whether you’re wrangling singletons or lazy-loading a database, sync.Once has your back.






4.1 Singleton Vibes: One Config to Rule Them All






The Problem



Picture this: your app needs a global config—say, API keys or timeouts. Without control, every goroutine might try to load it, and suddenly you’ve got five copies floating around, chewing up memory and confusing everyone. Chaos.






The Fix



sync.Once makes singletons a breeze. Here’s how I’ve done it:




package main

import (
"fmt"
"sync"
)

type Config struct {
Data string
}

var config *Config
var once sync.Once

func GetConfig() *Config {
once.Do(func() {
config = &Config{Data: "Loaded from somewhere fancy"}
fmt.Println("Config’s ready!")
})
return config
}

func main() {
for i := 0; i < 5; i++ {
go func() {
cfg := GetConfig()
fmt.Println("Gotcha:", cfg.Data)
}()
}
fmt.Scanln() // Hang out to see the action
}






Output:




Config’s ready!
Gotcha: Loaded from somewhere fancy
Gotcha: Loaded from somewhere fancy
Gotcha: Loaded from somewhere fancy
Gotcha: Loaded from somewhere fancy
Gotcha: Loaded from somewhere fancy









Why It Rocks





  • One and Done: Loads once, then every goroutine gets the same instance.


  • Lazy: Doesn’t load until someone asks—perfect for startup speed.



War Story: In a microservice, I used this to parse a YAML config. Before sync.Once, startup lagged at 200ms with duplicate reads. After? Down to 50ms. My team bought me coffee that day.






4.2 Lazy Loading: Database on Demand






The Problem



Database connections aren’t cheap—spinning up a pool takes time and resources. If every goroutine tries to make one, you’re toast. I’ve seen apps hit connection limits because of this.






The Fix



sync.Once lets you create the pool only when it’s needed. Check it:




package main

import (
"database/sql"
"fmt"
"sync"
_ "github.com/go-sql-driver/mysql"
)

var db *sql.DB
var once sync.Once

func GetDB() *sql.DB {
once.Do(func() {
var err error
db, err = sql.Open("mysql", "user:password@/dbname")
if err != nil {
panic(err) // Don’t do this IRL—handle it!
}
fmt.Println("DB’s up!")
})
return db
}

func main() {
for i := 0; i < 3; i++ {
go func() {
db := GetDB()
fmt.Println("Connections:", db.Stats().OpenConnections)
}()
}
fmt.Scanln()
}






Output:




DB’s up!
Connections: 1
Connections: 1
Connections: 1









Why It’s Clutch





  • On-Demand: No connection until the first call—saves resources.


  • Safe: No race conditions, no duplicates.



Pro Tip: In production, swap that panic for proper error handling. I learned that the hard way when a DB hiccup took down a service.






4.3 Big Jobs: Loading an ML Model






The Problem



Ever tried loading a machine learning model? It’s slow—like, “go get coffee” slow—and guzzles memory. If multiple goroutines kick off the load, you’re looking at crashes or a server bill that’ll make you cry.






The Fix



sync.Once keeps it sane:




package main

import (
"fmt"
"sync"
"time"
)

type Model struct {
Name string
}

var model *Model
var once sync.Once

func LoadModel() *Model {
once.Do(func() {
time.Sleep(2 * time.Second) // Fake a chunky load
model = &Model{Name: "Super Smart AI"}
fmt.Println("Model’s loaded!")
})
return model
}

func main() {
for i := 0; i < 4; i++ {
go func() {
m := LoadModel()
fmt.Println("Using:", m.Name)
}()
}
fmt.Scanln()
}






Output:




Model’s loaded!
Using: Super Smart AI
Using: Super Smart AI
Using: Super Smart AI
Using: Super Smart AI









Why It’s a Game-Changer





  • Resource Saver: One load, not four. In an image recognition app, this dropped memory from 4GB to 1GB.


  • Speed Boost: Startup went from “ugh” to “oh, nice” (60% faster in my case).



War Story: I once forgot to use sync.Once here, and a demo crashed mid-pitch. Never again.






4.4 Why You’ll Love It





  • Dead Simple: One line beats writing your own sync mess.


  • Bulletproof: Thread-safe out of the box.


  • Fast: After the first run, it’s basically free.






Quick Cheat Sheet




























Job Pain Level Why Use It?
Singleton Config Easy One instance, period
Lazy DB Pool Medium On-demand, no waste
ML Model Load Hard Big task, one shot





What’s Next?



These tricks have gotten me out of jams time and again. But sync.Once isn’t perfect—there are traps to dodge. In the next section, I’ll spill the beans on best practices and pitfalls, so you don’t repeat my mistakes. Trust me, I’ve got stories.






5. sync.Once Hacks and Traps: Learn from My Mess-Ups



By now, you’re probably itching to slap sync.Once into your next Go project—it’s awesome, right? But hold up: it’s simple until it isn’t. Over my decade with Go, I’ve found it’s a dream tool if you use it right. In this section, I’ll share some hard-won best practices and the pitfalls that bit me, complete with code and stories. Let’s make sure you nail it without the headaches I endured.






5.1 Best Practices: Do It Like a Boss






5.1.1 Wrap It Up in a Function



Don’t leave sync.Once flapping in the wind—stick it in a function. It’s cleaner, safer, and keeps your team from accidentally mucking with it.




package main

import (
"fmt"
"log"
"os"
"sync"
)

var logger *log.Logger
var once sync.Once

func GetLogger() *log.Logger {
once.Do(func() {
logger = log.New(os.Stdout, "INFO: ", log.LstdFlags)
fmt.Println("Logger’s live!")
})
return logger
}

func main() {
for i := 0; i < 3; i++ {
go func() {
l := GetLogger()
l.Println("Hey, it works!")
}()
}
fmt.Scanln()
}






Output:




Logger’s live!
INFO: 2025/03/28 10:00:00 Hey, it works!
INFO: 2025/03/28 10:00:00 Hey, it works!
INFO: 2025/03/28 10:00:00 Hey, it works!






Why It’s Smart: In a logging service, this trick kept my code tidy and stopped a junior dev from reusing once by mistake. Plus, it just feels right.






5.1.2 Don’t Panic Inside Do



Here’s a gotcha: if Do panics or fails, it’s game over—no retries. Your app’s left hanging with a half-baked resource.



Fix: Keep Do dumb and simple—move the heavy lifting elsewhere.




var db *sql.DB
var once sync.Once

func setupDB() error {
dbConn, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
return err
}
db = dbConn
return nil
}

func GetDB() *sql.DB {
once.Do(func() {
if err := setupDB(); err != nil {
log.Fatalf("DB blew up: %v", err) // Log, don’t panic
}
})
return db
}






Lesson: I once panicked in Do during a DB init, and my service died silently. Now I log and limp along—way better.






5.1.3 Add a Timeout with Context



If your init might hang (think flaky network), pair sync.Once with a context to avoid a forever stall.




func GetDBWithTimeout(ctx context.Context) *sql.DB {
once.Do(func() {
select {
case <-ctx.Done():
log.Println("DB init took too long—bailing!")
default:
db, _ = sql.Open("mysql", "user:password@/dbname")
log.Println("DB’s good!")
}
})
return db
}






War Story: A 5-second timeout saved my bacon when a DB server flaked during a prod deploy. No more “why’s it stuck?” calls at 2 a.m.






5.2 Pitfalls: Where I Tripped






5.2.1 Pitfall 1: Overloading Do



Trap: Stuffing Do with complex stuff (like network calls) that fails locks you out—no do-overs.



Bad Move:




var client *http.Client
var once sync.Once

func GetClient() *http.Client {
once.Do(func() {
resp, err := http.Get("http://example.com/config")
if err != nil {
panic(err) // Boom, no retry
}
client = &http.Client{}
})
return client
}






Fix: Pull the logic out.




func makeClient() (*http.Client, error) {
resp, err := http.Get("http://example.com/config")
if err != nil {
return nil, err
}
defer resp.Body.Close()
return &http.Client{}, nil
}

func GetClient() *http.Client {
once.Do(func() {
var err error
client, err = makeClient()
if err != nil {
log.Printf("Client failed: %v", err)
}
})
return client
}






Facepalm: Network blips killed an API service until I split this up. Now I retry outside Do if needed.






5.2.2 Pitfall 2: Local Once Syndrome



Trap: Declaring sync.Once inside a function makes a fresh one every call—oops, it runs every time.



Bad Move:




func oops() {
var once sync.Once // New each time
once.Do(func() {
fmt.Println("This *should* run once")
})
}

func main() {
for i := 0; i < 3; i++ {
go oops()
}
fmt.Scanln()
}






Output:




This *should* run once
This *should* run once
This *should* run once






Fix: Go global or tie it to a struct.




var once sync.Once

func yay() {
once.Do(func() {
fmt.Println("Now it’s really once")
})
}






Lesson: I wasted hours chasing “why’s this logging nonstop?” Scope matters, folks.






5.2.3 Real-Life Whoops: Redis Rookie Move



In a distributed app, I used sync.Once for a Redis client but didn’t handle failures. One bad connection, and half my features were DOA.



Fixed It:




var redisClient *redis.Client
var once sync.Once

func GetRedis() *redis.Client {
once.Do(func() {
client, err := initRedis()
if err != nil {
log.Printf("Redis flopped: %v", err)
return
}
redisClient = client
})
if redisClient == nil {
log.Println("Redis is down—retrying...")
// Add your retry logic here
}
return redisClient
}






Takeaway: Always plan for failure—sync.Once won’t save you from a bad day.






5.3 Quick Survival Guide

































Whoops Fix It Pro Tip
Fat Do
Split out the logic Keep it lean
Init Fails Log, don’t panic Stay alive
Local Once
Go global or struct Scope it right
Hangs Forever Add a context timeout Set a limit





What’s Next?



sync.Once is a champ, but it’s not foolproof—I’ve got the scars to prove it. With these hacks and traps in your pocket, you’re ready to rock it. Up next, we’ll wrap up with a quick recap and a peek at where sync.Once fits in Go’s concurrency future.






6. Wrapping Up sync.Once: Takeaways and What’s Next



We’ve been on a wild ride with sync.Once—from what it does, to how it ticks, to real-world wins and faceplants. It’s a tiny tool with big impact, and after a decade of Go, I can say it’s one of my go-to fixes for concurrency headaches. Let’s recap what makes it tick, boil down the must-knows, and peek at where it fits in Go’s ever-evolving concurrency scene.






6.1 The Recap: Why It’s a Keeper



sync.Once is like that friend who shows up clutch at 2 a.m.:





  • Simple: One once.Do and you’re golden—no sync spaghetti.


  • Fast: Locks once, then it’s basically free. My cache init went from “meh” to “whoa” quick.


  • Solid: Thread-safe by default, no repeats, no drama.



Whether you’re a newbie juggling goroutines or a grizzled vet scaling microservices, it’s a no-brainer for one-time jobs. I’ve used it to dodge config reloads, tame DB pools, and load AI models without torching servers. It’s small, but it punches way above its weight.






Big Lessons from My Toolbox





  1. Wrap It: Stick sync.Once in a function—keeps it clean and foolproof.


  2. Fail Smart: Don’t panic in Do; log and recover. Saved my app more than once.


  3. Scope It: Global or struct, not local—or you’ll be debugging repeats like I did.


  4. Time It: Add a context timeout for flaky setups. No more hangups.



These aren’t just tips—they’re scars from late-night bug hunts. Trust me, you’ll thank me later.






6.2 Looking Ahead: sync.Once in Go’s Future



Go’s concurrency game is heating up—cloud-native apps, microservices, multi-core CPUs—it’s a wild world out there. sync.Once holds strong with its “keep it simple” vibe, but as demands grow, we might see the sync package level up. Maybe finer-grained controls or lock-free twists? Still, sync.Once feels timeless—its elegance is tough to beat.






More Toys to Play With



Love sync.Once? Check these out:





  • sync.Pool: Reuse objects without the garbage collector crying.


  • sync.Map: Thread-safe key-value action for concurrent chaos.


  • sync.WaitGroup: Herd your goroutines like a pro.



They’re like the Avengers to sync.Once’s Captain America—different powers, same team.






My Two Cents



For me, sync.Once is the MVP of “get it done fast.” During a frantic deploy, it fixed a config bug in five minutes flat—hero status confirmed. It’s not perfect, though—dynamic reloads or retries need extra elbow grease. But when you need “once and done,” it’s your guy.






6.3 Parting Words



sync.Once is Go’s little gift to us: simple, powerful, and ready to tame concurrency gremlins. You’ve got the how, the where, and the “don’t do that” now—go slap it in your next project and feel the magic. Keep exploring Go’s concurrency goodies; the next big trick might be just around the corner. Happy coding!

2. Cyber Threat Intelligence & Forensik

CTI Threat Relationship Graph2 Knoten / 1 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten A Developer’s Guide to sync.Once: Your Go Concurrency Lifesaver

Thematisch verwandte Begriffe: Developers, Guide, syncOnce, Your · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-102241 | A vulnerability was determined in Netcore NAP930 0.1.241010.141410. Thi…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag