FP using Go's concurrency constructs
Given the expression result := function1() + function2(), parallelization means that we can run each function on a different CPU core and the total time will be approximately the time it takes for the most expensive function to return its result. Consider the following explanation for parallelization and concurrency:
- Parallelization: Executing multiple functions at the same time (in different CPU cores)
- Concurrency: Breaking a program into pieces that can be executed independently
I recommend that you check out the video Concurrency is Not Parallelism, by Rob Pike at https://player.vimeo.com/video/49718712. This is where he explains concurrency as a decomposition of a complex problem into smaller components, where inpidual components can be run simultaneously resulting in improved performance, assuming communication between them is managed.
Go enhances the concurrent execution of Goroutines with synchronization and messaging using channels and provides multiway concurrent control with the Select statement.
The following language constructs provide a model in Go for concurrent software construction that is easy to understand, use, and reason about:
- Goroutine: A lightweight thread managed by the Go runtime.
- Go statements: The go instruction that starts the execution of a function call as an independent concurrent thread of control, or Goroutine, in the same address space as the calling code.
- Channel: A typed conduit through which you can send and receive values with the channel operator, namely <-.
In the following code, data is sent to channel in the first line. In the second line, data is assigned the value received from channel:
channel <- data
data := <-channel
Since Go channels behave as FIFO queues, where the first items in are the first items out, and since the calculation for the next number in a Fibonacci sequence is a small component, it seems that our Fibonacci sequence function calculation is a great candidate for a concurrency implementation.
Let's give it a go. First, let's define a Channel function that uses a channel to perform Fibonacci calculations:
func Channel(ch chan int, counter int) {
n1, n2 := 0, 1
for i := 0; i < counter; i++ {
ch <- n1
n1, n2 = n2, n1 + n2
}
close(ch)
}
First, we declare the variables n1 and n2 to hold our initial sequence values of 0 and 1.
Then, we create a loop for the total number of times given. In each loop, we send the next sequential number to the channel and calculate the next number in the sequence, until we reach our counter value, which is the last sequential number in our sequence.
The following FibChanneled function creates a channel, namely ch, using the make() function and defines it as a channel that contains integers:
func FibChanneled(n int) int {
n += 2
ch := make(chan int)
go Channel(ch, n)
i := 0; var result int
for num := range ch {
result = num
i++
}
return result
}
We run our Channel (Fibonacci) function as a Goroutine and pass it the ch channel and the 8 number, which tells Channel to produce the first eight numbers from the Fibonacci sequence.
Next, we range over the channel and print any values that the channel produces for as long as the channel has not been closed.
Now, let's take a breather and examine what we've accomplished with our Fibonacci sequence examples.