Go的进程内发布/订阅速度提高了4-10倍
Event – Fast, In-Process Event Dispatcher

原始链接: https://github.com/kelindar/event

此Go包提供了一个高性能的进程内事件调度器,旨在解耦模块并启用异步事件处理。它拥有比使用通道(4x-10x)快得多的性能,并通过goroutines支持同步和异步处理。非常适合单个进程中的轻量级发布/订阅等场景,当您需要高吞吐量和低延迟时,它提供了一个简单、无依赖性的解决方案。 该包支持符合“event”接口的通用事件类型。它提供了一个默认的全局调度器,使用`On()`和`Emit()`可以轻松集成。或者,您可以创建特定的“Dispatcher”实例,并使用“Publish[T]()”和“Subscribe[T][)”进行更多控制。 请记住,此调度器不适用于进程间通信、事件持久性、复杂路由或需要事件回放的场景。它最适合与可管理的用户数量进行内部模块解耦。

The Hacker News discussion revolves around `Event`, a fast, in-process event dispatcher for Go, boasting 4-10x speed improvements over Go channels. The key lies in optimized throughput and broadcasting; consumers are grouped, a single lock manages each group, and events are replicated to each consumer's queue, allowing for up to 128 events to be consumed per lock/unlock cycle. In contrast, channels lock per element, resulting in more locking overhead. Commenters suggest adding methodology descriptions and comparisons to LMAX and Go channels to the README, along with benchmarks considering real-world scenarios like database calls. Channels are criticized for their general-purpose nature (many-to-many communication) imposing performance overhead, which is why the new event dispatcher implementation is so much faster. Others suggest that you need to consider what features were omitted to get the performance increase. Some find the implementation promising for building lightweight pub/sub systems within a single process, particularly in scenarios like game development where high throughput is beneficial. Alternative implementations include using channel closing for broadcasting.
相关文章

原文

kelindar/event
Go Version PkgGoDev License Coverage

Fast, In-Process Event Dispatcher

This package offers a high-performance, in-process event dispatcher for Go, ideal for decoupling modules and enabling asynchronous event handling. It supports both synchronous and asynchronous processing, focusing on speed and simplicity.

  • High Performance: Processes millions of events per second, about 4x to 10x faster than channels.
  • Generic: Works with any type implementing the Event interface.
  • Asynchronous: Each subscriber runs in its own goroutine, ensuring non-blocking event handling.

Use When:

  • ✅ Decoupling modules within a single Go process.
  • ✅ Implementing lightweight pub/sub or event-driven patterns.
  • ✅ Needing high-throughput, low-latency event dispatching.
  • ✅ Preferring a simple, dependency-free solution.

Not For:

  • ❌ Inter-process/service communication (use Kafka, NATS, etc.).
  • ❌ Event persistence, durability, or advanced routing/filtering.
  • ❌ Cross-language/platform scenarios.
  • ❌ Event replay, dead-letter queues, or deduplication.
  • ❌ Heavy subscribe/unsubscribe churn or massive dynamic subscriber counts.

Generic In-Process Pub/Sub

This repository contains a simple, in-process event dispatcher to be used to decouple internal modules. It provides a generic way to define events, publish and subscribe to them.

// Various event types
const EventA = 0x01

// Event type for testing purposes
type myEvent struct{
    Data string
}

// Type returns the event type
func (ev myEvent) Type() uint32 {
	return EventA
}

For convenience, this package provides a default global dispatcher that can be used with On() and Emit() package-level functions.

// Subcribe to event A, and automatically unsubscribe at the end
defer event.On(func(e Event) {
    println("(consumer)", e.Data)
})()

// Publish few events
event.Emit(newEventA("event 1"))
event.Emit(newEventA("event 2"))
event.Emit(newEventA("event 3"))

Using Specific Dispatcher

When publishing events, you can create a Dispatcher which is then used as a target of generic event.Publish[T]() and event.Subscribe[T]() functions to publish and subscribe to various event types respectively.

bus := event.NewDispatcher()

// Subcribe to event A, and automatically unsubscribe at the end
defer event.Subscribe(bus, func(e Event) {
    println("(consumer 1)", e.Data)
})()

// Subcribe to event A, and automatically unsubscribe at the end
defer event.Subscribe(bus, func(e Event) {
    println("(consumer 2)", e.Data)
})()

// Publish few events
event.Publish(bus, newEventA("event 1"))
event.Publish(bus, newEventA("event 2"))
event.Publish(bus, newEventA("event 3"))

It should output something along these lines, where order is not guaranteed given that both subscribers are processing messages asyncrhonously.

(consumer 2) event 1
(consumer 2) event 2
(consumer 2) event 3
(consumer 1) event 1
(consumer 1) event 2
(consumer 1) event 3

Please note that the benchmarks are run on a 13th Gen Intel(R) Core(TM) i7-13700K CPU, and results may vary based on the machine and environment. This one demonstrates the publishing throughput of the event dispatcher, at different number of event types and subscribers.

name                 time/op      ops/s        allocs/op    vs channels
-------------------- ------------ ------------ ------------ ------------------
1x1                  38.7 ns      25.9M        0             ✅ +4.2x
1x10                 13.0 ns      77.1M        0             ✅ +12x
1x100                12.2 ns      81.7M        0             ✅ +7.7x
10x1                 26.5 ns      37.7M        0             ✅ +6.3x
10x10                12.2 ns      82.3M        0             ✅ +7.8x
10x100               12.2 ns      82.0M        0             ✅ +6.6x

This project is licensed under the MIT License - see the LICENSE file for details.

联系我们 contact @ memedata.com