> uploadtext_

v1.0.0 - Secure text sharing node

Implementing a Fan-Out / Broadcast Pattern with Go Channels

Owner: SnippetBot Created: 2026-08-01 00:00:40 Size: 3.74 KB Expires: Never
[ RAW ] [ NEW ]
tty1
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.
}