Building a Basic Pub/Sub System with Go Channels
Owner: SnippetBot
Created: 2026-08-02 00:00:46
Size: 3.94 KB
Expires: Never
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package main
import (
"fmt"
"sync"
"time"
)
// Message is the data type for messages in our Pub/Sub system.
type Message struct {
Topic string
Payload string
}
// PubSub represents our simplified publish-subscribe system.
type PubSub struct {
mu sync.RWMutex
subscribers map[string][]chan Message // topic -> list of subscriber channels
}
// NewPubSub creates and returns a new PubSub instance.
func NewPubSub() *PubSub {
return &PubSub{
subscribers: make(map[string][]chan Message),
}
}
// Subscribe allows a client to subscribe to a given topic.
// It returns a read-only channel where messages for that topic will be delivered.
func (ps *PubSub) Subscribe(topic string) <-chan Message {
ps.mu.Lock()
defer ps.mu.Unlock()
ch := make(chan Message, 10) // Buffered channel for subscriber
ps.subscribers[topic] = append(ps.subscribers[topic], ch)
fmt.Printf("Subscribed to topic '%s'. Total subscribers: %d
", topic, len(ps.subscribers[topic]))
return ch
}
// Unsubscribe removes a subscriber channel from a topic.
func (ps *PubSub) Unsubscribe(topic string, subCh <-chan Message) {
ps.mu.Lock()
defer ps.mu.Unlock()
if channels, ok := ps.subscribers[topic]; ok {
for i, ch := range channels {
if ch == subCh {
// Remove the channel from the slice
ps.subscribers[topic] = append(channels[:i], channels[i+1:]...)
close(ch) // Close the subscriber's channel
fmt.Printf("Unsubscribed from topic '%s'. Remaining subscribers: %d
", topic, len(ps.subscribers[topic]))
return
}
}
}
}
// Publish sends a message to all subscribers of a specific topic.
func (ps *PubSub) Publish(msg Message) {
ps.mu.RLock() // Use RLock because we are only reading the map for subscribers
defer ps.mu.RUnlock()
if channels, ok := ps.subscribers[msg.Topic]; ok {
// Send message to all subscribers in separate goroutines to avoid blocking
// if a subscriber channel is full or slow.
for _, ch := range channels {
go func(c chan Message) {
select {
case c <- msg:
// Message sent
case <-time.After(100 * time.Millisecond):
// Timeout if subscriber channel is full.
// This prevents a slow subscriber from blocking others indefinitely.
fmt.Printf("Warning: Dropped message for topic '%s' due to slow subscriber.
", msg.Topic)
}
}(ch)
}
} else {
fmt.Printf("No subscribers for topic '%s'. Message dropped.
", msg.Topic)
}
}
func main() {
ps := NewPubSub()
// Subscriber 1
sub1 := ps.Subscribe("news")
go func() {
for msg := range sub1 {
fmt.Printf("Subscriber 1 received: [%s] %s
", msg.Topic, msg.Payload)
}
fmt.Println("Subscriber 1 channel closed.")
}()
// Subscriber 2
sub2 := ps.Subscribe("news")
go func() {
for msg := range sub2 {
fmt.Printf("Subscriber 2 received: [%s] %s
", msg.Topic, msg.Payload)
time.Sleep(200 * time.Millisecond) // Simulate slow subscriber
}
fmt.Println("Subscriber 2 channel closed.")
}()
// Subscriber 3 for a different topic
sub3 := ps.Subscribe("updates")
go func() {
for msg := range sub3 {
fmt.Printf("Subscriber 3 received: [%s] %s
", msg.Topic, msg.Payload)
}
fmt.Println("Subscriber 3 channel closed.")
}()
time.Sleep(100 * time.Millisecond) // Give subscribers time to start
ps.Publish(Message{Topic: "news", Payload: "Breaking news: Gophers spotted!"})
ps.Publish(Message{Topic: "updates", Payload: "System update available."})
ps.Publish(Message{Topic: "news", Payload: "Local weather: Sunny with a chance of channels."})
ps.Publish(Message{Topic: "alerts", Payload: "Emergency alert!"}) // No subscriber for 'alerts'
time.Sleep(1 * time.Second) // Let messages be processed
ps.Unsubscribe("news", sub1) // Subscriber 1 leaves
ps.Publish(Message{Topic: "news", Payload: "More news after unsubscribe."})
time.Sleep(500 * time.Millisecond)
fmt.Println("Main: Exiting.")
// In a real application, you'd have a mechanism to close all subscriber channels
// when the PubSub system shuts down. For this example, we let main exit.
}