Implementing a Fan-Out / Broadcast Pattern with Go Channels
Owner: SnippetBot
Created: 2026-08-01 00:00:40
Size: 3.74 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
package main
import (
"fmt"
"sync"
"time"
)
// Broadcaster manages multiple subscribers and broadcasts messages to them.
type Broadcaster struct {
subscribers []chan string // Each subscriber gets its own channel
mu sync.RWMutex
}
// NewBroadcaster creates a new Broadcaster instance.
func NewBroadcaster() *Broadcaster {
return &Broadcaster{}
}
// Subscribe adds a new subscriber and returns a channel to receive messages.
func (b *Broadcaster) Subscribe() <-chan string {
b.mu.Lock()
defer b.mu.Unlock()
// Use a buffered channel for subscribers to avoid blocking the broadcaster
// if a subscriber is slow, but ensure messages are delivered.
subChan := make(chan string, 5) // Buffer size can be adjusted
b.subscribers = append(b.subscribers, subChan)
fmt.Printf("New subscriber added. Total: %d
", len(b.subscribers))
return subChan
}
// Unsubscribe removes a subscriber channel.
func (b *Broadcaster) Unsubscribe(subChan <-chan string) {
b.mu.Lock()
defer b.mu.Unlock()
for i, ch := range b.subscribers {
// Compare channels by memory address (this might be tricky with non-pointers or different types)
// For a simple case, direct comparison works. More robust would be to use a map with IDs.
if ch == subChan { // This comparison works for channels
close(ch) // Close the channel to signal no more messages
b.subscribers = append(b.subscribers[:i], b.subscribers[i+1:]...)
fmt.Printf("Subscriber removed. Total: %d
", len(b.subscribers))
return
}
}
}
// Broadcast sends a message to all active subscribers.
func (b *Broadcaster) Broadcast(message string) {
b.mu.RLock() // Use RLock as we are only reading the slice of subscribers
defer b.mu.RUnlock()
var activeSubscribers []chan string
for _, sub := range b.subscribers {
select {
case sub <- message:
activeSubscribers = append(activeSubscribers, sub) // Keep active subscribers
default:
// If a subscriber's channel is full, we might drop the message for it.
// Or, we could consider it a "slow" subscriber and remove it,
// or block here if guaranteed delivery is critical.
fmt.Printf("Dropping message for a slow subscriber: %s
", message)
// For this example, we'll just not add it to the 'active' list if it blocks
// so it will effectively be cleaned up on next iteration if this were dynamic.
// A proper cleanup would involve Unsubscribe or a more robust tracking mechanism.
}
}
// In a real system, you might clean up blocked/closed channels here.
// For simplicity, this example just demonstrates the broadcast loop.
}
func main() {
b := NewBroadcaster()
// Subscriber 1
sub1 := b.Subscribe()
go func() {
for msg := range sub1 {
fmt.Printf("Subscriber 1 received: %s
", msg)
}
fmt.Println("Subscriber 1 channel closed.")
}()
// Subscriber 2
sub2 := b.Subscribe()
go func() {
for msg := range sub2 {
time.Sleep(150 * time.Millisecond) // Simulate slow processing
fmt.Printf(" Subscriber 2 received: %s
", msg)
}
fmt.Println(" Subscriber 2 channel closed.")
}()
// Subscriber 3 (added later)
var sub3 <-chan string
go func() {
time.Sleep(500 * time.Millisecond) // Wait a bit before subscribing
sub3 = b.Subscribe()
for msg := range sub3 {
fmt.Printf(" Subscriber 3 received: %s
", msg)
}
fmt.Println(" Subscriber 3 channel closed.")
}()
time.Sleep(100 * time.Millisecond) // Give goroutines time to start
b.Broadcast("Hello everyone!")
b.Broadcast("Another update!")
time.Sleep(300 * time.Millisecond)
b.Unsubscribe(sub1) // Remove subscriber 1
b.Broadcast("Final message for active subscribers!")
time.Sleep(1 * time.Second) // Give time for messages to be processed
// To cleanly shut down, you'd iterate and close all subscriber channels
// For this example, we'll just let main exit.
}