Learning golang 04 - Goroutine
Learning Go routine from go tour
Goroutines
Goroutine is a lightweight thread managed by the Go runtime. a “thread” does not mean virtual thread or even multiple thread. it is like async await. meaning there is one main thread and multiple seperate “threads” spawned which are technically running in the same thread instead of spawning one for every process. Each of these threads are blocked when a blocking process is run. and repicked when it is done.
go f(x,y,z)
runs a new go routine with f(x,y,z)
The evaluation for f, x, y, z happens in the current routine and the execution happens in the new go routine.
package main
import {
"fmt"
"time"
}
type Ab int
func say(s string) {
for i:=0; i<10 ; i++ {
time.Sleep(100*time.Millisecond)
fmt.Println(s)
}
}
func main(){
go say("hello")
// ^ This is run in a different thread
say("world")
}
Channels
channels are typed conduit through which you can send and receive values with the certain operators.
each channel is a seperately spawned goroutines that awaits for new data.
ch <- v
v:= <-ch
Like maps and slices, channels must be created before being used
ch := make(chan int)
Data flows in the direction of the arrows
By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.
package go
import "fmt"
func sum(s []int, c chan int){
// ^^^^ note the 'chan' keyword
sum:=0
for i,v := range(s) {
fmt.Println(i)
sum+=v
}
c <- sum
// ^ sending the sum to the channel
}
func main() {
s:=[]int{1,2,3,44,6,7,8,9,0}
c:= make(chan int) // making a channel which is able to receive int type
go sum(s[:len(s)/2],c) //creating seperate runtimes
go sum(s[len(s)/2:],c) //creating seperate runime
x,y := <-c, <-c // getting the variable from the channel
fmt.Println(x,y,x+y)
}
Buffered Channels
Channels can be buffered to have a certain amount of value. like a numbered of list of elements in the channel
package main
import (
"fmt"
)
func main(){
ch := make(chan int, 2) // -> The 2 say that,only 2 int type variables can be held at any given time
// ^^ note the new variable
c <- 12
c <- 11
c <- 10 //REMOVE!!! This would throw error as max only 2 can be assigned
fmt.Println(<-c) //prints 12
c <- 10 //This works!! 🤯 because the first one has been relieved
fmt.Println(<-c) //prints 11
fmt.Println(<-c) //prints 10
}
Range and Close
A sender can close a channel to indicate that there are no more variables to be sent. Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression. Like how we do for errors in functions
v, ok := <-ch
ok
is false
if there are no more values to receive and the channel is closed.
The loop for i := range c {}
loops infinitely until the channel is empty
Note:
- Only the sender should close a channel, never the receiver. Sending on a closed channel will cause a panic
- Channels aren’t like files; you don’t usually need ot close them. Closing is only necessary when the receiver must be told there are no more value
package main
import(
"fmt"
)
func fibonacci(n int, c chan int){
x,y := 0,1
for i := 0; i<n ; i++{
c <- x
x,y = y, x+y
}
close(c) //closes the channel
}
func main(){
c := make(chan int, 10)
go fibonacci(10, c)
for i := range c {
// ^^^^^^^ this creates an iterator of sorts with the stream coming from the channel, until the channel is closed.
fmt.Println(i)
}
}
Select
A select is similar to a switch statement. But a select statement works on channels. it uses whatever channel is available now.
select statements blocks and waits on multiple communication operations. A select statement picks one at random if multiple channels are available.
package main
import "fmt"
func fibonacci(c, quit chan int){
x,y := 0,1
for {
select{
// ^^ goroutine is blocked here, until atleast a channel is ready
case c <- x:
x, y = y, x+y
case <- quit:
fmt.Println("quit")
return
}
}
}
func main(){
c:= make(chan int)
quit := make(chan int)
go func x(c , quit chan int){
// ^^ starting a go routine here, to push data to the channel
for i:=0; i<10; i++{
fmt.Println(<-c)
}
}(c,quit)
fibonacci(c,quit)
}
Default Selection
The default case in a select is run if no other case is ready.
use a default case to try a send or receive without blocking.
select {
case i:= <- c:
//use i
default:
// receiving from c would block
}
This is helpful to use as a fallback
package main
import "fmt"
func main(){
tick := time.Tick(100 * time.Millisecond)
boom := timeAfter(500 * time.Millisecond)
for {
select {
case <- tick:
fmt.Println("tick.")
case <- boom:
fmt.Println("Boom!")
default:
// ^^^^^^^ using default would make this function an unblocking thread
fmt.Println(" .")
time.sleep(50* time.Millisecond)
}
}
}