Concurrency, Goroutines, and Channels in Go: A Study

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    Concurrency, Goroutines, and Channels in Go: A Study

    Abstract

    This article explores the fundamental concepts of concurrency in Go, focusing on Goroutines and Channels.
    • Concurrency: The ability to handle multiple tasks in overlapping time periods. It does not imply that tasks are executed simultaneously, but rather that they are managed in a way that allows progress on multiple tasks within a given timeframe.
    • Goroutine: The smallest unit of execution in Go, enabling concurrent operations. Goroutines are lightweight threads managed by the Go runtime and can be created using the go keyword.
    • Channel: A conduit for communication and synchronization between Goroutines. Channels allow Goroutines to send and receive values of a specified type.
      • Send: Sending a value to a channel using the syntax ch .
      • Receive: Receiving a value from a channel using the syntax value := .


    Implementing a Simple Example Similar to Docker Pull





    package main

    import (
    "fmt"
    "math/rand"
    "os"
    "strconv"
    "sync"
    "time"
    )


    func main() {
    // Create random seed
    rand.Seed(time.Now().UnixNano())

    // Set concurrency count
    if len(os.Args) 2 {
    fmt.Println("Usage: go run main.go [number]")
    return
    }
    n, err := strconv.Atoi(os.Args[1])
    if err != nil || n 0 {
    fmt.Println("Invalid number. Please provide a positive integer.")
    return
    }

    // A slice use to track the progress of each goroutine. (1 - 100)
    progress := make([]int, n)

    // A mutex to prevent conflicts during progress updates.
    var mu sync.Mutex

    // WaitGroup for waiting until each goroutine has completed
    var wg sync.WaitGroup
    wg.Add(n)

    // Create n goroutine
    for i := 0; i n; i++ {
    go func(idx int) {
    defer wg.Done()
    // Each goroutine will update its progress until it reaches 100%
    for {
    // The loop breaks if progress reaches 100%
    mu.Lock()
    if progress[idx] >= 100 {
    mu.Unlock()
    break
    }
    // Add a random progress value (1-10%)
    inc := rand.Intn(10) + 1
    progress[idx] += inc
    if progress[idx] > 100 {
    progress[idx] = 100
    }
    mu.Unlock()

    // Sleep for a random duration between 100 and 500 milliseconds
    time.Sleep(time.Duration(rand.Intn(400)+100) * time.Millisecond)
    }
    }(i)
    }

    // A channel to signal that all goroutines have finished
    // The channel uses struct{} as a signal. The actual data type is irrelevant since only the signal is needed.
    done := make(chan struct{})
    go func() {
    wg.Wait() // Wait until all wg(WaitGroup) are done.
    done struct{}{} // And send data to done channel. This channel is referenced by select statement below.
    }()

    // Periodically draw the progress status on the screen
    ticker := time.NewTicker(100 * time.Millisecond)
    defer ticker.Stop()

    for {
    select {
    case ticker.C:
    // Clear the screen and redraw
    printProgress(progress, &mu)
    case done:
    // Display the final state and exit
    printProgress(progress, &mu)
    return
    }
    }
    }

    // printProgress is a function that displays the progress of each indicator
    // Prevent concurrent access to progress data using a mutex
    func printProgress(progress []int, mu *sync.Mutex) {
    mu.Lock()
    defer mu.Unlock()

    // Clear the screen using ANSI escape sequences
    // \033[H : Move the cursor to the home position
    // \033[2J : Clear the screen
    fmt.Print("\033[H\033[2J")
    for i, p := range progress {
    // Set the total width of the progress bar to 50 characters
    width := 50
    // Number of "*" characters based on progress percentage
    stars := p * width / 100
    spaces := width - stars
    fmt.Printf("%d.[%s%s] %d%%\n", i+1, repeat("*", stars), repeat(" ", spaces), p)
    }
    }

    // repeat: Returns concatenated string by repeating string "s" "count" times
    func repeat(s string, count int) string {
    result := ""
    for i := 0; i count; i++ {
    result += s
    }
    return result
    }







    The program functions as follows:




    What I Learned

    • Using Goroutines with the go Keyword: The go keyword allows the creation of goroutines, enabling concurrent execution of functions.
    • Concurrency with Goroutines: Goroutines facilitate concurrent processing, as demonstrated by managing the progress of multiple indicators simultaneously in this example.


    Questions and Future Investigations

    • WaitGroup:
      • Understanding that wg.Add increments the counter and wg.Done decrements it, and wg.Wait blocks until the counter reaches zero.
      • Investigate additional functionalities and use cases of WaitGroup.
    • Mutex:
      • Understanding that mutexes prevent race conditions by ensuring exclusive access to shared resources during updates.
      • Curious about the specific issues that arise when mutexes are not used, such as inconsistent variable states.
      • Interested in intentionally creating race conditions to observe and understand their effects.
    • Testing Methods:
      • Learned about the go test -race flag for detecting race conditions.
      • Plan to explore this testing method further to ensure code safety and reliability.




    More...
Working...