Go Routine
Goroutine is a lightweight thread maintained by Go Runtime. To make a goroutine, just add the keyword go right before the function we're about to call.
In Golang, every program in minimum is driven by at least one goroutine called main goroutine:
- When
main()
is executed, this will become the first goroutine - When main goroutine stops, other goroutines will also stop
Goroutines run concurrently with other goroutines
Example of a simple goroutine:
package main
func greet(name string) {
fmt.Println("Hello,", name)
}
func main() {
fmt.Println("Main thread started")
go greet("Yehezkiel")
go greet("Wiradhika")
time.Sleep(100 * time.Millisecond) // without this, the goroutine won't be able to run
fmt.Println("Main thread ended")
}
Note: without the time.Sleep(100 * time.Millisecond)
, the goroutine won't run since the main goroutine is over before the other goroutine able to run. We need a blocking condition, for example using time.Sleep(100 * time.Millisecond)
.
(Source: geeksforgeeks)
Yet another example:
package main
func apiCallA(start time.Time) {
time.Sleep(100 * time.Millisecond)
fmt.Println("API Call A started at:", time.Since(start))
}
func apiCallB(start time.Time) {
time.Sleep(100 * time.Millisecond)
fmt.Println("API Call B started at:", time.Since(start))
}
func sequential() {
start := time.Now()
apiCallA(start)
apiCallB(start)
time.Sleep(100 * time.Millisecond)
fmt.Println("Sequential ends at", time.Since(start))
}
func concurrent() {
start := time.Now()
go apiCallA(start)
go apiCallB(start)
time.Sleep(100 * time.Millisecond)
fmt.Println("Concurrent ends at", time.Since(start))
}
func main() {
fmt.Println("Main thread started")
sequential()
concurrent()
time.Sleep(100 * time.Millisecond)
fmt.Println("Main thread ended")
}
Sequential Output:
Main thread started
API Call A started at: 100.2245ms
API Call B started at: 200.9909ms
Sequential ends at 302.0439ms
Main thread ended
Concurrent output:
Main thread started
API Call B started at: 100.5045ms
Concurrent ends at 100.5045ms
API Call A started at: 100.5045ms
Main thread ended
Channel
You can't just pass values between one thread to another the usual way with concurrency in Go, here's an example:
package main
import "fmt"
func addOne(n int) int {
return n + 1
}
func main() {
res := go addOne(1)
fmt.Println(res)
}
You can't just run the above go script. For this, you'll need something called channel.
Channel Definition
Channel is a type of data that is used to communicate between goroutines. You could use it to send or receive data between goroutines. It's usually used to exchange data from one function to another.
Channel is developed based on CSP concept (Communicating Sequential Process). CSP is a communication model used to manage concurrency. It splits concurrency into two parts, which is proccess and channel. Process is a function that runs independently while Channel is a type of data used to send and receive data.
Channel Declaration
Channel is just a type of data, here's how you declare it:
names := make(chan string)
Here's how you exchange data using channel:
package main
import "fmt"
func main() {
names := make(chan string)
addName := func(name string) {
names <- name
}
go addName("Yehezkiel")
go addName("John")
go addName("Jane")
for i := 0; i < 3; i++ {
fmt.Println(<-names)
}
}
Here's the output (it's not always like this, see: goroutine concept):
Jane
Yehezkiel
John
Channel Synchronization
We could utilize channel to do Synchronization by using channel as blocking or waiting for the other processes to complete. Here's an example:
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(1)
var start = time.Now()
fmt.Println("main started at time ", time.Since(start))
c := make(chan string)
go func() {
// time.Sleep(10 * time.Millisecond)
fmt.Printf("hello from goroutine, at time %v\n", time.Since(start))
c <- "goroutine say hi"
}()
fmt.Printf("goroutine sent this: '%v'. At time %v\n", <-c, time.Since(start))
fmt.Printf("main stopped at time %v\n", time.Since(start))
}
From the above code, you don't have to use time.Sleep(10 * time.Millisecond)
so the goroutine could run before main thread terminates.
Output:
main started at time 0s
hello from goroutine, at time 537.1µs
goroutine sent this: 'goroutine say hi'. At time 537.1µs
main stopped at time 537.1µs
Channel as Parameter
You could use channel as parameter with the following syntax:
func createHelloMessage(msgs chan string, name string) {
msg := fmt.Sprintf("Hello, %v", name)
msgs <- msg
}
Here's the full example:
package main
import "fmt"
func createHelloMessage(msgs chan string, name string) {
msg := fmt.Sprintf("Hello, %v", name)
msgs <- msg
}
func main() {
msgs := make(chan string)
names := []string{"Yehezkiel", "Jane", "John"}
for _, i := range names {
go createHelloMessage(msgs, i)
}
for i := 0; i < len(names); i++ {
fmt.Println(<-msgs)
}
}
Output:
Hello, John
Hello, Yehezkiel
Hello, Jane