Stateful Goroutines

This Go code demonstrates concurrent read and write operations on a state map using goroutines and channels. The program tracks the number of read and write operations using atomic operations. Let's break down the code with inline comments:

package main

import (
	"fmt"
	"math/rand"
	"sync/atomic"
	"time"
)

// readOp represents a read operation.
type readOp struct {
	key  int
	resp chan int
}

// writeOp represents a write operation.
type writeOp struct {
	key  int
	val  int
	resp chan bool
}

func main() {
	// Variables to track the number of read and write operations using atomic counters.
	var readOps uint64
	var writeOps uint64

	// Channels for communication between goroutines.
	reads := make(chan readOp)
	writes := make(chan writeOp)

	// Goroutine simulating a stateful object with read and write operations.
	go func() {
		// Initial state map.
		var state = make(map[int]int)

		for {
			select {
			case read := <-reads:
				// Handling read operation: responding with the value from the state map.
				read.resp <- state[read.key]
			case write := <-writes:
				// Handling write operation: updating the state map with the provided key-value pair.
				state[write.key] = write.val
				write.resp <- true
			}
		}
	}()

	// Goroutines simulating concurrent read operations.
	for r := 0; r < 100; r++ {
		go func() {
			for {
				// Generating a random key for the read operation.
				read := readOp{
					key:  rand.Intn(5),
					resp: make(chan int),
				}
				reads <- read
				<-read.resp
				// Incrementing the readOps counter using atomic operation.
				atomic.AddUint64(&readOps, 1)
				time.Sleep(time.Millisecond)
			}
		}()
	}

	// Goroutines simulating concurrent write operations.
	for w := 0; w < 10; w++ {
		go func() {
			for {
				// Generating random key-value pair for the write operation.
				write := writeOp{
					key:  rand.Intn(5),
					val:  rand.Intn(100),
					resp: make(chan bool),
				}
				writes <- write
				<-write.resp
				// Incrementing the writeOps counter using atomic operation.
				atomic.AddUint64(&writeOps, 1)
				time.Sleep(time.Millisecond)
			}
		}()
	}

	// Allowing some time for goroutines to perform read and write operations.
	time.Sleep(time.Second)

	// Loading and printing the final values of readOps and writeOps counters.
	readOpsFinal := atomic.LoadUint64(&readOps)
	fmt.Println("readOps:", readOpsFinal)
	writeOpsFinal := atomic.LoadUint64(&writeOps)
	fmt.Println("writeOps:", writeOpsFinal)
}

Output

readOps: 6723
writeOps: 680

Explanation:

  1. readOp and writeOp structures represent read and write operations, respectively.

  2. The main goroutine initializes channels for read and write operations, and a goroutine simulating a stateful object.

  3. Concurrent goroutines are created to simulate both read and write operations. The read operations retrieve a value from the state, and the write operations update the state.

  4. The atomic package is used to perform atomic operations on counters (readOps and writeOps) to track the number of read and write operations.

  5. The program runs for some time (time.Sleep(time.Second)) to allow goroutines to perform operations.

  6. The final values of the readOps and writeOps counters are loaded atomically and printed.

The code showcases a scenario where multiple goroutines concurrently read and write to a shared state, and atomic operations are used to safely track the number of operations. The use of channels ensures proper synchronization between goroutines.

Last updated