Basic In-Memory Publish-Subscribe System with Channels
Owner: SnippetBot
Created: 2026-09-09 00:00:50
Size: 4.19 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
138
139
140
141
142
package main
import (
"fmt"
"sync"
"time"
)
// Broker manages subscriptions and publishes messages.
type Broker struct {
mu sync.RWMutex
subscribers map[string][]chan string // Topic -> list of subscriber channels
}
func NewBroker() *Broker {
return &Broker{
subscribers: make(map[string][]chan string),
}
}
// Subscribe registers a new subscriber for a given topic.
// Returns a channel on which messages for the topic will be received.
func (b *Broker) Subscribe(topic string) <-chan string {
msgCh := make(chan string, 1)
b.mu.Lock()
defer b.mu.Unlock()
b.subscribers[topic] = append(b.subscribers[topic], msgCh)
fmt.Printf("Subscriber joined topic '%s'. Total subscribers: %d
", topic, len(b.subscribers[topic]))
return msgCh
}
// Publish sends a message to all subscribers of a given topic.
func (b *Broker) Publish(topic, message string) {
b.mu.RLock()
defer b.mu.RUnlock()
subs := b.subscribers[topic]
fmt.Printf("Publisher: Publishing '%s' to topic '%s' for %d subscribers.
", message, topic, len(subs))
for _, subCh := range subs {
// Use select with default to avoid blocking if a subscriber is slow
select {
case subCh <- message:
// Message sent successfully
default:
fmt.Printf("Warning: Subscriber on topic '%s' is full, dropping message: %s
", topic, message)
}
}
}
// Unsubscribe removes a subscriber from a topic and closes its channel.
func (b *Broker) Unsubscribe(topic string, subCh <-chan string) {
b.mu.Lock()
defer b.mu.Unlock()
subs := b.subscribers[topic]
for i, ch := range subs {
if ch == subCh {
b.subscribers[topic] = append(subs[:i], subs[i+1:]...)
close(ch) // Close the subscriber's channel
fmt.Printf("Subscriber left topic '%s'. Remaining: %d
", topic, len(b.subscribers[topic]))
return
}
}
}
func main() {
broker := NewBroker()
// Subscriber 1 for "news"
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
newsSubCh := broker.Subscribe("news")
for msg := range newsSubCh {
fmt.Printf("News Subscriber 1: %s
", msg)
if msg == "The End" {
fmt.Println("News Subscriber 1: Done receiving.")
return
}
}
fmt.Println("News Subscriber 1: Channel closed.")
}()
// Subscriber 2 for "news" and "alerts"
wg.Add(1)
go func() {
defer wg.Done()
newsSubCh := broker.Subscribe("news")
alertSubCh := broker.Subscribe("alerts")
for {
select {
case msg, ok := <-newsSubCh:
if !ok { fmt.Println("News Subscriber 2 (news): Channel closed."); newsSubCh = nil }
else { fmt.Printf("News Subscriber 2: %s
", msg) }
case msg, ok := <-alertSubCh:
if !ok { fmt.Println("News Subscriber 2 (alerts): Channel closed."); alertSubCh = nil }
else { fmt.Printf("Alerts Subscriber 2: %s
", msg) }
}
if newsSubCh == nil && alertSubCh == nil {
fmt.Println("News Subscriber 2: All channels closed, exiting.")
return
}
}
}()
// Publisher sends messages
time.Sleep(time.Millisecond * 100) // Give subscribers time to register
broker.Publish("news", "Breaking News: Go channels are awesome!")
broker.Publish("alerts", "Urgent: System maintenance tonight.")
time.Sleep(time.Millisecond * 200)
broker.Publish("news", "New feature release next week.")
broker.Publish("alerts", "Reminder: Backups are running.")
time.Sleep(time.Millisecond * 200)
// Unsubscribe a subscriber (e.g., News Subscriber 1)
// This requires keeping track of the channel returned by Subscribe for News Subscriber 1
// For simplicity, let's assume we can't easily get it here directly without modifying the example.
// In a real app, you'd store the returned 'newsSubCh' from `broker.Subscribe` in a variable.
broker.Publish("news", "Final update: Server reboot complete.")
broker.Publish("news", "The End") // Signal for a specific subscriber to stop
// Give time for messages to be processed before main exits
time.Sleep(time.Second * 1)
// A more robust shutdown would involve signaling all subscribers to exit
// and then closing the internal channels in the broker.
wg.Wait() // Wait for subscribers to finish
fmt.Println("Main: All messages published and subscribers processed.")
// In a real system, you'd also need a way to close all subscriber channels
// managed by the broker when the broker itself shuts down.
}