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
- Requires a parent context in this example Ryan uses
- 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
- i.e bank accounts can:
- 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:
- https://www.youtube.com/watch?v=VkGQFFl66X4&t=433s