diff --git a/counter/README.md b/counter/README.md new file mode 100644 index 0000000..8597129 --- /dev/null +++ b/counter/README.md @@ -0,0 +1,38 @@ +# counter + +线程安全的 `string → int64` 键值计数器,适合并发计数场景(按 key 累加/读取/覆盖)。 + +> 迁移自 go-hua/single 并更名:包 `single` → `counter`,类型 `Single` → `Counter`, +> 方法签名不变(Init/Get/Incr/Set)。 + +## 用法 + +```go +import ( + "sync" + + "git.zeroonesoft.cn/golib/zogo/counter" +) + +var c counter.Counter +c.Init() + +// 并发安全累加 +var wg sync.WaitGroup +for i := 0; i < 100; i++ { + wg.Add(1) + go func() { defer wg.Done(); c.Incr("pv", 1) }() +} +wg.Wait() +c.Get("pv") // 100 + +// Set 覆盖并返回旧值 +old := c.Set("pv", 0) +``` + +完整可运行例程:[examples/counter/main.go](../examples/counter/main.go) + +## 注意 + +- **使用前必须 Init**,未初始化直接 `Incr/Set` 会 panic(nil map 写入)。 +- 单实例内部持锁;跨实例无共享状态。 diff --git a/counter/counter.go b/counter/counter.go new file mode 100644 index 0000000..d6fa6ee --- /dev/null +++ b/counter/counter.go @@ -0,0 +1,41 @@ +// Package counter 提供线程安全的 string→int64 键值计数器,适合并发计数场景。 +// +// 迁移自 go-hua/single 并更名(原类型 Single → Counter),方法签名不变。 +package counter + +import "sync" + +// Counter 线程安全的键值计数器(使用前需调用 Init) +type Counter struct { + mu sync.Mutex + values map[string]int64 +} + +// Init 初始化内部存储(每个 Counter 实例使用前调用一次) +func (c *Counter) Init() { + c.values = make(map[string]int64) +} + +// Get 读取指定 key 的值,key 不存在返回 0 +func (c *Counter) Get(key string) int64 { + c.mu.Lock() + defer c.mu.Unlock() + return c.values[key] +} + +// Incr 按指定增量累加 key 的值,返回累加后的值 +func (c *Counter) Incr(key string, value int64) int64 { + c.mu.Lock() + defer c.mu.Unlock() + c.values[key] += value + return c.values[key] +} + +// Set 覆盖指定 key 的值,返回旧值 +func (c *Counter) Set(key string, val int64) int64 { + c.mu.Lock() + defer c.mu.Unlock() + old := c.values[key] + c.values[key] = val + return old +} diff --git a/counter/counter_test.go b/counter/counter_test.go new file mode 100644 index 0000000..1010692 --- /dev/null +++ b/counter/counter_test.go @@ -0,0 +1,57 @@ +package counter + +import ( + "sync" + "testing" +) + +func TestIncrConcurrent(t *testing.T) { + var c Counter + c.Init() + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + c.Incr("pv", 1) + }() + } + wg.Wait() + + if got := c.Get("pv"); got != 100 { + t.Errorf("并发 100 次 Incr 后 = %d, want 100", got) + } +} + +func TestSet(t *testing.T) { + var c Counter + c.Init() + + c.Set("k", 10) + old := c.Set("k", 20) + if old != 10 { + t.Errorf("Set 返回旧值 = %d, want 10", old) + } + if got := c.Get("k"); got != 20 { + t.Errorf("Set 后 Get = %d, want 20", got) + } +} + +func TestGetMissing(t *testing.T) { + var c Counter + c.Init() + if got := c.Get("not-exist"); got != 0 { + t.Errorf("不存在的 key Get = %d, want 0", got) + } +} + +func TestKeysIndependent(t *testing.T) { + var c Counter + c.Init() + c.Incr("a", 1) + c.Incr("b", 2) + if c.Get("a") != 1 || c.Get("b") != 2 { + t.Errorf("不同 key 应相互独立: a=%d b=%d", c.Get("a"), c.Get("b")) + } +} diff --git a/examples/counter/main.go b/examples/counter/main.go new file mode 100644 index 0000000..393d755 --- /dev/null +++ b/examples/counter/main.go @@ -0,0 +1,34 @@ +// counter 包示例:线程安全的键值计数器(并发场景下按 key 累加/读取/覆盖) +package main + +import ( + "fmt" + "sync" + + "git.zeroonesoft.cn/golib/zogo/counter" +) + +func main() { + var counters counter.Counter + counters.Init() + + // 并发安全计数 + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + counters.Incr("pv", 1) + }() + } + wg.Wait() + fmt.Println("并发 100 次 Incr 后 pv:", counters.Get("pv")) + + // Set 覆盖并返回旧值 + old := counters.Set("pv", 0) + fmt.Println("Set 返回旧值:", old, "新值:", counters.Get("pv")) + + // 不同 key 相互独立 + counters.Incr("uv", 1) + fmt.Println("uv:", counters.Get("uv")) +}