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. }