feat: basic RwMutex implementation

This commit is contained in:
2025-09-26 08:35:21 +07:00
parent 147e25b116
commit 1ce17bff21
3 changed files with 106 additions and 0 deletions

View File

@@ -1,2 +1,74 @@
# custom-rwmutex
## Overview
`CustomRwMutex` is a lightweight, type-safe wrapper around Gos 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; theyre 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 its 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** — theyre 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
})
```