1ce17bff21f764a6d2e8afe383693bc02bdc8a06
custom-rwmutex
Overview
CustomRwMutex is a lightweight, type-safe wrapper around Go’s standard sync.RWMutex that provides safer, more ergonomic access patterns through handler functions. Instead of manually calling Lock()/RLock() and Unlock()/RUnlock(), you pass a function to be executed under the appropriate lock, ensuring proper resource management and reducing the risk of deadlocks or unlocked access.
This pattern enforces scope-bound locking, similar to a defer-based RAII pattern, making concurrent code more readable and less error-prone.
Features
- ✅ Thread-safe: Built on
sync.RWMutex, guarantees safe concurrent access. - ✅ Handler-based API: Prevents manual lock/unlock mistakes.
- ✅ Error propagation: Functions passed in can return errors; they’re propagated up cleanly.
- ✅ No external dependencies: Pure Go, zero dependencies.
- ✅ Minimal overhead: Just a thin wrapper around standard library primitives.
Real-World Example: Shared Config
type Config struct {
Host string
Port int
}
func main() {
config := &Config{Host: "localhost", Port: 8080}
mu := custom_rwmutex.NewCustomRwMutex()
// Update config (writer)
err := mu.WriteHandler(func() error {
config.Host = "api.example.com"
config.Port = 443
return nil
})
if err != nil {
log.Fatal(err)
}
// Read config (reader)
err = mu.ReadHandler(func() error {
fmt.Printf("Current config: %s:%d\n", config.Host, config.Port)
return nil
})
if err != nil {
log.Fatal(err)
}
}
Note
: Since
configis captured in closures, ensure it’s not modified outside the lock context. For full safety, consider returning copies of data fromReadHandler.
Why Use CustomRwMutex?
Problem with sync.RWMutex |
How CustomRwMutex Solves It |
|---|---|
Manual Lock()/Unlock() can be forgotten or mismatched |
Automatically handled via defer |
| Risk of leaving locks held due to early returns | defer guarantees unlock even on error |
| No built-in error handling for operations | Functions return errors; easily propagated |
| Verbose and error-prone patterns | Clean, functional-style API |
Best Practices
- ✅ Keep handlers short and fast — long operations under write locks block all readers and writers.
- ✅ Avoid blocking calls like
time.Sleep()or HTTP requests in locked sections. - ✅ Return errors from handlers — they’re preserved and returned from
ReadHandler/WriteHandler. - ✅ Do not hold references to external data outside the handler unless you are certain they are thread-safe or copied.
- ✅ For heavy read workloads, consider returning a copy of the data instead of exposing references:
var config Data
err := mu.ReadHandler(func() error {
result := config // copy the data
// do work on result
return nil
})
Description
Languages
Go
100%