Notes: GoLang

Notes: GoLang

GoLang Notes

Outline

Iterate Over String

Switch Case

Execute Shell Commands

Iterate_Over_String

var name string;
name = "GoLang";
for index, value := range name {
  fmt.Printf("Index %d Char %c\n", index, value);
  }

Switch-Case

for _, value := range "Golang" {
  switch value {
    case 'G' : fmt.Println("Case G executed")
    case 'l' : fmt.Println("Case I executed")
   }
  }

Execute-Shell-Commands

Ref: https://pkg.go.dev/os/exec@go1.17.6

package main

import (
  "fmt"
  "os/exec"
)

func main() {

  command := "uptime"
  output, error := exec.Command(command).Output()
  if error != nil {
    fmt.Println("Received error while executing the command")
  } else {
    fmt.Printf("Command %s output %s ", command, output)
  }
}

Output

❯❯ sysadmin_programming_tasks git:(my-code-go) 12:02 go run execute_uptime_command_1.go
Command uptime output  12:02:42 up  1:29,  1 user,  load average: 0.34, 0.44, 0.63

Go Routines and Concurrency

[Refernces]

Notes

  • to use go routines just use the place go before the code.
  • for example if you want to execute function like count("Sheep") in async using go routines then you modify the line as
go count("sheep")
  • so what will happen is that go will start executing this in the background and move to next line.
  • dont add your go routine as last line of your main method. As per go programming if go ccoroutine reaches to last line then program will exit.
  • to avoid that you can use a trick placing fmt.Scanln() and that make the main program wais for user to enter the input.
  • But to do this properly we can leverage sync pacakge Waitgroup interface
package main

import (
	"fmt"
	"time"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	wg.Add(1)

	go func() {
		count("sheep")
		wg.Done()
	}() // anonymous function.
}

func count(thing string) {
	for i := 1; i <= 5; i++ {
		fmt.Println(i, thing)
		time.Sleep(time.Millisecond * 500)
	}
}

Channel

  • channels are for go routines to communicate with each other.
  • we can channel a type and send messages through that channel
  • we can send a channel through a channel
  • define channel with chan
  • channel kind of block the communication, if channel is sending a message then it will wait till the message received.
  • and if a channel is supposed to receieve a message, then it will wait till that message is received.
  • to send a channel c <- thing
  • to receive from a channel thing := <- c
  • the syncronize go routines we use channels, because they block execution by waiting for to receive the data/response.
  • A channel received can send/ receive at only one time. To receive perform this receive/sending multiple times we have iterate over the data.
  • As a sender you know how much data you have to send, but as a receiver you dont know how much data you should receive, so always be in a state of waiting for the data if your channel is waiting for the data.
  • As a sender, you can close the channel using close() function by passing channel as argument and that will close the channel at the receiver end as well.
  • at the receiver end, we get a bool true if channel is opened, so we can monitor that to know whether channel opened or not at the receiver end.
package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan string) // creating a string type channel
	go count("sheep", c) // passing channel as argument to function 
	for msg := range c { // instead of checking on bool like msg, open we are checking for data thats coming out of the channel 
		fmt.Println(msg)
	}
}

func count(thing string, c chan string) {
	for i := 1; i <= 5; i++ {
		c <- thing
		time.Sleep(time.Millisecond * 500 )
	}

	close(c)
}

Channel Blocking

  • if you are trying to sending data via channel, as you are sender, there must be a receiver created already, for example
package main

import (
	"fmt"
)

func main() {
	c := make(chan string)
	c <- "hello" // you are sending data to channel, but receiver not created yet, so your program blocked here, as this line exxecuted its trying to send the data but no receiver is ready to receive the data. 

	msg := <- c
	fmt.Println(msg)

}
  • so resolve issues like this, we can define size of the channel like block, and channel will take those many block and hold it and lets compiler to move forward, in simple words it wont block the execution as long as up to reserved block data given, for example
package main

import (
	"fmt"
)

func main() {
	c := main(chan string, 2) // 2 bugger block
	c <- "Hello" // block 1
	c <- "World" // block 2

	msg := <- c
	fmt.Println(msg)

	msg := <- c
	fmt.Println(msg)
}
  • Now this code works fine, but if you try to send another buffer block of data via channel like c <- "3rd block" then it wont work, this time as it cant hold it must need a receiver.

Channel Selectors

  • if you are trying to read data from multiple channels like
for {
	fmt.Println(<-c1)
	fmt.Println(<-C2)
}

then what happens here is that, till C2 channel completes its execution (like till it receives the data, it will block the execution of c1 )

package main

import (
	"fmt"
)

func main() {

	// creating two channels
	c1 := make(chan string)
	c2 := make(chan string)

	go func() {
		for {
			c1 <- "Every 500ms"
			time.Sleep(time.Millisecond * 500 )
		}
	} ()

	go func() {
		for {
			c2 <- "Every two seconds"
			time.Sleep(time.Second * 2 )
		}
	} ()

	for {
		fmt.Println(<-c1)
		fmt.Println(<-c2)
	}

}

So to avoid blocking of one channel as its waiting for the response we can use select case statements to read the data from the channel which ever is ready with data received.

package main

import (
	"fmt"
)

func main() {

	// creating two channels
	c1 := make(chan string)
	c2 := make(chan string)

	go func() {
		for {
			c1 <- "Every 500ms"
			time.Sleep(time.Millisecond * 500 )
		}
	} ()

	go func() {
		for {
			c2 <- "Every two seconds"
			time.Sleep(time.Second * 2 )
		}
	} ()

	for {
		select {
		case msg1 := <- c1 :
			fmt.Println(msg1)
		case msg2 := <- c2 :
			fmt.Println(msg2)
		}
	}
}

Workerpool Pattern

By using workpool patterns we can trigger multiple go routines without blocking any channel or thread. Need to dig into this concept more like what are the use cases, below code fibnoci calculation for 100 numbers


package main

import (
	"fmt"
)

func main() {
	jobs := make(chan int, 100)
	results := make(chan int, 100)

	go worker(jobs, results)
	go worker(jobs, results)
	go worker(jobs, results)
	go worker(jobs, results)

	for i := 0; i < 100; i++ {
		jobs <- i
	}
	close(jobs)

	for j := 0; j < 100; j++ {
		fmt.Println(<-results)
	}
}

func worker(jobs <-chan int, results chan<- int) {
	for n := range jobs {
		results <- fib(n)
	}
}

func fib(n int) int {
	if n <= 1 {
		return n
	}

	return fib(n-1) + fib(n-2)
}

If you want to execute a function only for a specific period of time, go go-routines with channel is a great combination. All you have to do is trigger another go routine which do the sleep functionality and then close the channel on which the communication right now happening.


    package main

    import (
        "fmt"
        "time"
    )

    func main() {
        c := make(chan string) // creating a string type channel
        go count("sheep", c)   // passing channel as argument to function
        go sleep(c)
        for msg := range c { // instead of checking on bool like msg, open we are checking for data thats coming out of the channel
            fmt.Println(msg)
        }
    }

    func sleep(c chan string) {
        time.Sleep(time.Second * 5)
        fmt.Println("in sleep method")
        close(c)
        fmt.Println("in sleep method")
    }

    func count(thing string, c chan string) {
        for i := 1; i <= 5; i++ {
            c <- thing
            time.Sleep(time.Millisecond * 500)
        }

    }

0 comments:

Post a Comment