Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
219: Using sync.Pool for Allocation Reduction
I've seen this happen in countless code reviews: a developer tries to implement a "cache" using sync.Pool to store expensive-to-create objects, only to spend three days wondering why their "cache" is randomly emptying itself every few minutes. They treat it like a map with a mutex, expecting the objects to stay there until they manually remove them.
The Myth: sync.Pool is a Persistent Object Cache
If you think of sync.Pool as a way to ensure an object is always available or to limit the number of instances of a resource (like a database connection pool), you're in for a surprise. The Go runtime is allowed to clear a sync.Pool at any time—usually during a garbage collection cycle. I can't tell you how many times I've had to explain that the runtime views the pool as a collection of "disposable" items. If the GC decides it needs memory, everything in your pool is fair game for deletion.
If you need a guarantee that an object persists, a sync.Pool is the wrong tool. Using it as a cache will lead to unpredictable performance spikes because your "cached" items will vanish, forcing the pool to call your New function repeatedly under load.
The Reality: Reducing GC Pressure for Transient Objects
The real purpose of sync.Pool is to reduce the frequency of allocations for objects that are used very briefly and then thrown away. Think of it as a way to reuse "scratch space." When you use a sync.Pool, you aren't trying to save the object; you're trying to save the allocation cost and the subsequent work the garbage collector has to do to clean up thousands of short-lived objects.
A classic example is a bytes.Buffer. If you're formatting a log message or building a string in a high-throughput loop, allocating a new buffer every single time creates a mountain of garbage for the GC to climb. Instead, you can pull a buffer from a pool, use it, reset it, and put it back.
package main
import (
"bytes"
"fmt"
"sync"
)
// We create a pool for bytes.Buffers.
// The 'New' function is called when the pool is empty.
var bufferPool = sync.Pool{
New: func() any {
fmt.Println("Allocating new buffer...")
return new(bytes.Buffer)
},
}
func formatMessage(id int, msg string) string {
// Get a buffer from the pool. It comes back as 'any', so we cast it.
buf := bufferPool.Get().(*bytes.Buffer)
// CRITICAL: Always reset the buffer before using it.
// The buffer might contain data from a previous user.
buf.Reset()
buf.WriteString("ID: ")
buf.WriteString(fmt.Sprintf("%d", id))
buf.WriteString(" - Msg: ")
buf.WriteString(msg)
result := buf.String()
// Put it back for the next goroutine to use.
bufferPool.Put(buf)
return result
}
func main() {
for i := 0; i < 5; i++ {
fmt.Println(formatMessage(i, "Hello Go!"))
}
}
Notice the buf.Reset() call. This is where most people trip up. Because the pool gives you a used object, that object still contains whatever the previous goroutine wrote to it. If you forget to reset, your data will bleed across requests, which is a nightmare to debug.
- Get(): Retrieves an item. If the pool is empty, it calls
New. - Put(): Returns an item to the pool.
- The Trade-off: You're trading a bit of complexity and the risk of state contamination (if you forget to reset) for a significant drop in CPU usage spent on garbage collection.
I usually suggest sync.Pool only after you've run a pprof heap profile and confirmed that a specific, short-lived object is causing a bottleneck. Don't optimize prematurely; just keep this tool in your back pocket for when the GC starts stealing too many of your CPU cycles.
📋 Practical Task
Exercise: High-Throughput JSON Fragment Builder
You are building a system that generates small JSON fragments for a telemetry stream. Currently, the system allocates a new bytes.Buffer for every single fragment, causing high GC overhead during peak traffic.
Your Task: Implement a FragmentPool using sync.Pool to reuse buffers. Your solution must:
- Define a
sync.Poolwith aNewfunction that returns a pointer to abytes.Buffer. - Implement a function
BuildFragment(sensorID string, value float64) stringthat:- Retrieves a buffer from the pool.
- Ensures the buffer is cleared of old data.
- Writes a JSON string in the format:
{"id": "sensor_1", "val": 23.5}. - Returns the final string and returns the buffer to the pool.
- Ensure that there are no data leaks between calls (i.e., the second call to
BuildFragmentshould not contain any data from the first call).
There are no comments for now.