feat: basic RwMutex implementation
This commit is contained in:
72
README.md
72
README.md
@@ -1,2 +1,74 @@
|
||||
# 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
|
||||
|
||||
```go
|
||||
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 `config` is captured in closures, ensure it’s not modified outside the lock context. For full safety, consider returning copies of data from `ReadHandler`.
|
||||
|
||||
## 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:
|
||||
|
||||
```go
|
||||
var config Data
|
||||
err := mu.ReadHandler(func() error {
|
||||
result := config // copy the data
|
||||
// do work on result
|
||||
return nil
|
||||
})
|
||||
```
|
||||
Reference in New Issue
Block a user