From e6455c67ecf98f1356021d1ab2350c50911d5887 Mon Sep 17 00:00:00 2001 From: Sebastian Steger Date: Tue, 14 Apr 2026 14:25:46 +0000 Subject: [PATCH] add comments --- go/05-concurrency/deadlock.go | 30 +++++++++++++-- go/05-concurrency/mergeSort.go | 69 ++++++++++++++++++++++++++++++++++ go/05-concurrency/select.go | 16 ++++++-- 3 files changed, 107 insertions(+), 8 deletions(-) diff --git a/go/05-concurrency/deadlock.go b/go/05-concurrency/deadlock.go index 56c7e55..2184034 100644 --- a/go/05-concurrency/deadlock.go +++ b/go/05-concurrency/deadlock.go @@ -6,6 +6,15 @@ import ( "time" ) +/** + * This function reads integers from a receive-only channel and prints them to the console. + * It simulates some work by sleeping for 100 milliseconds after printing each integer. + * The function uses a done channel to signal when it has finished processing all integers from the input channel. + * When the input channel is closed and all integers have been printed, the function sends a value to the done channel to indicate that it is done. + * + * @param ci A receive-only channel of integers that the function will read from and print. The function will continue to read from the channel until it is closed. + * @param done A send-only channel of booleans that the function will use to signal when it has finished processing the input channel. The function will send a value to this channel when it is done. + */ func printChannel(ci <-chan int, done chan<- bool) { defer func() { done <- true }() for i := range ci { @@ -14,6 +23,14 @@ func printChannel(ci <-chan int, done chan<- bool) { } } +/** + * This function is similar to printChannel but uses a WaitGroup instead of a done channel to signal completion. + * The WaitGroup allows us to wait for the goroutine to finish without needing a separate channel for signaling. + * + * @param ci A receive-only channel of integers that the function will read from and print. The function will continue to read from the channel until it is closed. + * @param wg A pointer to a sync.WaitGroup that will be used to signal when the goroutine has finished processing the channel. + * The WaitGroup should have its counter incremented before calling this function, and this function will call Done() on the WaitGroup when it finishes. + */ func printWaitgroup(ci <-chan int, wg *sync.WaitGroup) { defer func() { wg.Done() }() for i := range ci { @@ -24,11 +41,15 @@ func printWaitgroup(ci <-chan int, wg *sync.WaitGroup) { func main() { - c := make(chan int) + c := make(chan int) //no capacity, unbuffered channel. This means that sends to the channel will block until another goroutine is ready to receive from the channel, and receives from the channel will block until another goroutine sends a value to the channel. + // This is useful for synchronizing between goroutines, as it ensures that the sender and receiver are coordinated. + // Use a channel named done to signal when the printChannel goroutine has finished processing the input channel. + // This allows us to wait for the goroutine to complete before exiting the main function. //done := make(chan bool) //go printChannel(c, done) + // Use a WaitGroup to wait for the printWaitgroup goroutine to finish instead of using a done channel. wg := sync.WaitGroup{} wg.Add(1) go printWaitgroup(c, &wg) @@ -36,8 +57,9 @@ func main() { c <- 1 c <- 2 c <- 3 - close(c) + close(c) // Close the channel to signal to the printChannel goroutine that there are no more values to read. + // This will cause the for loop in printChannel to exit and the function to send a value to the done channel, allowing the main function to wait for it to finish. - //<-done - wg.Wait() + //<-done //alternative to using a done channel, we can wait for the WaitGroup to finish instead. + wg.Wait() // Wait for the printWaitgroup goroutine to finish before exiting the main function. } diff --git a/go/05-concurrency/mergeSort.go b/go/05-concurrency/mergeSort.go index 53e2ebd..85697d6 100644 --- a/go/05-concurrency/mergeSort.go +++ b/go/05-concurrency/mergeSort.go @@ -8,17 +8,31 @@ import ( "time" ) +/* +* +* Sorts a slice of float64 values using the merge sort algorithm in parallel with channels. +* +* @param data The slice of float64 values to be sorted. +* @param sorted The output channel where the sorted values will be sent. This channel should be buffered to avoid blocking. +* The function will close this channel when sorting is complete. + */ func mergeSortChannelsInternal(data []float64, sorted chan<- float64) { + + // close the sorted channel when we're done sending sorted values. This ensures that the receiver can detect when all sorted values have been sent and can stop waiting for more data. defer close(sorted) if len(data) <= 10000 { + // For small slices, sort them sequentially to avoid the overhead of creating goroutines and channels. This threshold is chosen to balance the overhead of concurrency with the benefits of parallelism. sortedData := make([]float64, len(data)) copy(sortedData, data) mergeSortSequential(sortedData) + + // Send sorted values to the output channel. We iterate over the sorted slice and send each value to the sorted channel. for _, x := range sortedData { sorted <- x } } else { + // For larger slices, split them into two halves and sort each half in parallel using channels. mid := len(data) / 2 left := data[:mid] right := data[mid:] @@ -28,27 +42,40 @@ func mergeSortChannelsInternal(data []float64, sorted chan<- float64) { go mergeSortChannelsInternal(left, sortedLeft) go mergeSortChannelsInternal(right, sortedRight) + + // Merge the sorted halves and send the merged sorted values to the output channel. mergeChannels(sortedLeft, sortedRight, sorted) } } +/** + * Merges two sorted channels into a single sorted channel. + * + * @param a The first sorted channel. + * @param b The second sorted channel. + * @param out The output channel where the merged sorted values will be sent. This channel should be buffered to avoid blocking. + */ func mergeChannels(a, b <-chan float64, out chan<- float64) { var okA, okB bool var valA, valB float64 for { + // If we don't have a value from channel a, try to read from it. if !okA { valA, okA = <-a } + // If we don't have a value from channel b, try to read from it. if !okB { valB, okB = <-b } + // If both channels are closed and we've read all values, break the loop. if !okA && !okB { break } + // If one channel is closed, we can only read from the other channel. if !okA { out <- valB okB = false @@ -61,6 +88,7 @@ func mergeChannels(a, b <-chan float64, out chan<- float64) { continue } + // Both channels have values, compare them and send the smaller one to the output channel. if valA < valB { out <- valA okA = false @@ -71,6 +99,11 @@ func mergeChannels(a, b <-chan float64, out chan<- float64) { } } +/** + * Sorts a slice of float64 values using the merge sort algorithm in parallel with channels. + * + * @param data The slice of float64 values to be sorted. The sorting is done in-place, so the original slice will be modified. + */ func mergeSortChannels(data []float64) { sorted := make(chan float64, 1000) go mergeSortChannelsInternal(data, sorted) @@ -85,19 +118,33 @@ func mergeSortChannels(data []float64) { copy(data, sortedData) } +/** + * Sorts a slice of float64 values using the merge sort algorithm in parallel. + * + * @param data The slice of float64 values to be sorted. The sorting is done in-place, so the original slice will be modified. + */ func mergeSortParallel(data []float64) { + + // Base case: if the slice has 10000 or fewer elements, sort it sequentially. This threshold is chosen to balance + // the overhead of creating goroutines with the benefits of parallelism. For small slices, the overhead of goroutines + // may outweigh the performance gains, so we sort them sequentially. if len(data) <= 10000 { mergeSortSequential(data) return } + // Split the slice into two halves. The mid index is calculated as the length of the data divided by 2, which gives us the midpoint of the slice. mid := len(data) / 2 left := data[:mid] right := data[mid:] + // Use a WaitGroup to wait for both halves to be sorted before merging. The WaitGroup allows us to synchronize the completion + // of the two goroutines that will sort the left and right halves in parallel. var wg sync.WaitGroup wg.Add(2) + // Sort left and right halves in parallel using goroutines. Each goroutine will call mergeSortParallel + // on its respective half and then signal when it's done using the WaitGroup. go func() { mergeSortParallel(left); wg.Done() }() go func() { mergeSortParallel(right); wg.Done() }() @@ -106,35 +153,57 @@ func mergeSortParallel(data []float64) { copy(data, merge(left, right)) } +/** + * Sorts a slice of float64 values using the merge sort algorithm sequentially. + * + * @param data The slice of float64 values to be sorted. The sorting is done in-place, so the original slice will be modified. + */ func mergeSortSequential(data []float64) { + + // Base case: if the slice has 0 or 1 element, it's already sorted. if len(data) <= 1 { return } + // Recursive case: split the slice into two halves, ... mid := len(data) / 2 left := data[:mid] right := data[mid:] + // ... sort each half recursively, ... mergeSortSequential(left) mergeSortSequential(right) + // ... and then merge the sorted halves back together. copy(data, merge(left, right)) } +/** + * Merges two sorted slices into a single sorted slice. + * + * @param left The first sorted slice. + * @param right The second sorted slice. + * @return A new slice containing all elements from both input slices, sorted in ascending order. + */ func merge(left, right []float64) []float64 { + // Create a new slice to hold the merged result. The capacity is set to the sum of the lengths of left and right for efficiency. result := make([]float64, 0, len(left)+len(right)) i, j := 0, 0 + // Merge elements from left and right until one of the slices is exhausted. for i < len(left) && j < len(right) { if left[i] < right[j] { + // Append the smaller element from left to the result and move the pointer i. result = append(result, left[i]) i++ } else { + // Append the smaller element from right to the result and move the pointer j. result = append(result, right[j]) j++ } } + // Append any remaining elements from left or right. The ... operator is used to expand the slice into individual elements. result = append(result, left[i:]...) result = append(result, right[j:]...) diff --git a/go/05-concurrency/select.go b/go/05-concurrency/select.go index c6c70ed..ad272e1 100644 --- a/go/05-concurrency/select.go +++ b/go/05-concurrency/select.go @@ -12,15 +12,19 @@ func main() { ch2 := make(chan string) go func() { + // Simulate some work by sleeping for a random duration between 1 and 5 seconds before sending a message to ch1. time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second) ch1 <- "Message from channel 1" }() go func() { + // Simulate some work by sleeping for a random duration between 1 and 5 seconds before sending a message to ch2. time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second) ch2 <- "Message from channel 2" }() + // Create multiple dynamic channels and send messages to them in separate goroutines. + // This demonstrates how we can use select to wait for messages from a variable number of channels. count := 10 var channels []chan string for i := range count { @@ -32,13 +36,17 @@ func main() { }() } + // Fan-in pattern: Create a single channel to receive messages from multiple channels. + // This allows us to use select to wait for messages from all channels in a single case. fanIn := make(chan string) for _, ch := range channels { go func(c chan string) { - fanIn <- <-c + fanIn <- <-c // Read a message from the dynamic channel and send it to the fanIn channel. A temporary variable is needed to capture the current value of ch. }(ch) } + // Use select to wait for messages from multiple channels. The select statement allows us to wait on multiple channel operations + // and proceed with the one that is ready first. select { case msg1 := <-ch1: println("Received:", msg1) @@ -46,9 +54,9 @@ func main() { println("Received:", msg2) case msg := <-fanIn: println("Received:", msg) - case <-time.After(3 * time.Second): + case <-time.After(3 * time.Second): // The time.After function returns a channel that will send the current time after the specified duration. This case will be selected if no other channels are ready within 3 seconds, allowing us to handle a timeout scenario. println("Timeout occurred") - default: - println("Nothing") + //default: // The default case is executed if none of the channels are ready. Not to be used in this example, as it would cause the select statement to proceed immediately. + // println("Nothing") } }