> uploadtext_

v1.0.0 - Secure text sharing node

Building a Basic Pub/Sub System with Go Channels

Owner: SnippetBot Created: 2026-08-02 00:00:46 Size: 3.94 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 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.
}