Golang: Channels, Context, and Interfaces

Created: 2023-03-2311:56

Channels

Used when you want to transfer data between go routines

Channel basics

  • By default, a channel has no space inside of it
    • Sort of like a portal, data must enter & exit the channel at the same time
    • This can be achieved using go routines to concurrently add data to the channel while retrieving the data
  • Constructed with dataChan := make(chan <DataType>) where <DataType> can be an int, string, custom data type, etc

Buffered Channels

  • You can make channels buffered, meaning that you add limited space to the channel to store values
    • dataChan := make(chan <DataType>, 2)
    • This allows for two values to be stored in the channel

Sending & Receiving Data To / From a Channel

  • Data can be sent to the channel:
    • dataChan <- 789
  • Data can be retrieved from the channel:
    • n := <- dataChan

Channels in Multi-Threaded Go Routines

  • Rule: If you want to retrieve data to an unbuffered channel you have to simultaneously send data to that same channel
  • Go routines allow for concurrent programming which would allow you to add data in one thread, while also pulling data out of the channel at the same time, see below:
func main () {
    dataChan := make(chan int) // channel specific to ints

    go func() {
        for i := 0; i < 1000; i++ {
            dataChan <- i
        }
        close(dataChan)
    }()

    for n := range dataChan {
        fmt.Printf("n = %d\n", n)
    }
}

Using Channels in a More Real-World Example

func main () {
    dataChan := make(chan int) // channel specific to ints

    go func() {
        for i := 0; i < 1000; i++ {
            // function waits one sec & returns an int
            result := DoWork() 
            dataChan <- result
        }
        close(dataChan)
    }()

    for n := range dataChan {
        fmt.Printf("n = %d\n", n)
    }
}
  • Looping over a function call then does some work, then returns a value
  • If the function were to run through on only two threads we could expect that the output to the console when we print the value to be line by line with a brief pause between printouts.
  • By putting the inner portion of the for loop that resides in the anonymous function in its own go routine we will end up with +1000 threads all doing the one second of work simultaneously and returning all at once:
    go func() {
        // Create wait group to keep track of child processes
        wg := sync.WaitGroup{}
        for i := 0; i < 1000; i++ {
            // Add to wg counter for tracking
            wg.Add(1)
            // each itr gets its own thread for the
            // function call to DoWork 
            go func () {
                // After function completes decrement wg
                // counter by one
                defer wg.Done()
                // function waits one sec & returns an int
                result := DoWork() 
                dataChan <- result
            }
        }
        // Wait here until all child processes finish
        wg.Wait()
        close(dataChan)
    }()
  • Now instead of waiting one thousand seconds to get the returned values, we wait only one!
  • The addition of the WaitGroup allows the program to keep track of the child processes and keeps the parent process from closing out before they have all finished.

Context

Adding timeouts or cancellations to go routines

  • Use case example:
    • when making HTTP requests cancel the request if it has not been completed after a certain amount of time
  • Many libraries include a WithContext option for many different functions.
    • This allows you to add a context for different cancellation scenarios
    • ex: http.NewRequestWithContext()
  • Using context.WithTimeout(parent context, timeout):
    • Requires a parent context in this example Ryan uses context.Background()
    • The timeout can be set using something like: time.Milliseconds * 100 for 100 milliseconds
  • When building your own contexts, you are able to utilize other contexts as parent contexts of the one you are creating to leverage the cancellation abilities of more than one context

Interfaces

Blueprinting for how your app or service is going to behave

  • Go does not have true OOP and interfaces help to get us closer to that feeling
  • Interfaces define what a given struct is capable of doing
    • i.e bank accounts can:
      • get balance
      • deposit
      • withdraw
  • example:
    go type IBankAccount interface { GetBalance() int Deposit(amount int) Withdraw(amount int) error }
  • The IBankAccount interface can act as a type for different bank account structs as structs can implement the capabilities of a given interface

References:

  1. https://www.youtube.com/watch?v=VkGQFFl66X4&t=433s

Leave a Reply

Your email address will not be published. Required fields are marked *