Go Tutorial Part 4 (Interfaces, Routines, Channels, Concurrency in Go)

GO - Part 4

Credit goes to - Master the fundamentals and advanced features of the Go Programming Language (Golang), on Udemy by Stephen Grider.
I have tried some snippets on my own and some are referring to the snippets developed while going through the course.

Code Repo: https://github.com/namitsharma99/goLangTutorials




Interfaces
- - - - - - - - - 

Need:
Whenever there is a logic change in a function, it has to be re-written.


For example, maintaining 2 chatboxes :
A.
1. type hindiBot struct

2. func (hindiBot) getGreeting() string // return "namaste"

3. func printGreeting(hb hindiBot) // similar code - fmt.Println(hb.getGreeting())

B.
1. type englishBot struct

2. func (englishBot) getGreeting() string // return "hello"

3. func printGreeting(eb englishBot) // similar code - fmt.Println(eb.getGreeting())


Code --

package main

import "fmt"

type hindiBot struct {}
type englishBot struct {}

func main()  {

}

//func (hb hindiBot) getGreeting() string {
func (hindiBot) getGreeting() string {  // since no hb value is read
  return "namaste!!"
}

// func (eb englishBot) getGreeting() string {
func (eb englishBot) getGreeting() string { // since no eb value is read
  return "hello!!"
}

func printGreeting(hb hindiBot) {
  fmt.Println(hb.getGreeting())
}

func printGreeting(eb englishBot) {
  fmt.Println(eb.getGreeting())
}

ERROR::

$ go build main.go 
# command-line-arguments
./main.go:26:6: printGreeting redeclared in this block
previous declaration at ./main.go:22:23


FIX 1:

package main

import "fmt"

type hindiBot struct {}
type englishBot struct {}

func main()  {
  hb := hindiBot{}
  eb := englishBot{} // gives error

  printGreeting(hb)
  printGreeting(eb)

}

//func (hb hindiBot) getGreeting() string {
func (hindiBot) getGreeting() string {  // since no hb value is read
  return "namaste!!"
}

// func (eb englishBot) getGreeting() string {
func (eb englishBot) getGreeting() string { // since no eb value is read
  return "hello!!"
}

// ERROR
/* func printGreeting(hb hindiBot) {
  fmt.Println(hb.getGreeting())
}

func printGreeting(eb englishBot) {
  fmt.Println(eb.getGreeting())
} */
// printGreeting redeclared in this block

// using single print method
func printGreeting(hb hindiBot) {
  fmt.Println(hb.getGreeting())
}

ERROR::
$ go build main.go 
# command-line-arguments
./main.go:13:16: cannot use eb (type englishBot) as type hindiBot in argument to printGreeting


FIX 2: 

-- using interfaces

package main

import "fmt"

// the interface fix
type bot interface {
  getGreeting() string
}


type hindiBot struct {}
type englishBot struct {}

func main()  {
  hb := hindiBot{}
  eb := englishBot{}

  printGreeting(hb)
  printGreeting(eb)

}

//func (hb hindiBot) getGreeting() string {
func (hindiBot) getGreeting() string {  // since no hb value is read
  return "namaste!!"
}

// func (eb englishBot) getGreeting() string {
func (eb englishBot) getGreeting() string { // since no eb value is read
  return "hello!!"
}

// ERROR
/* func printGreeting(hb hindiBot) {
  fmt.Println(hb.getGreeting())
}

func printGreeting(eb englishBot) {
  fmt.Println(eb.getGreeting())
} */
// printGreeting redeclared in this block

func printGreeting(b bot) {
  fmt.Println(b.getGreeting())
}


O/P
- - -

$ ./main 
namaste!!
hello!!


WORKING:

Interface identifies based on the func associated.

type bot interface {
  getGreeting() string
}

This declares that any type, having getGreeting() func and returning the string, automatically becomes a member of this interface

Since hb and eb above, are now the members, they can call the printGreeting() func easily using the bot interface

Note:
------
Argument type can also be put in the clause along with the type check as follows :

type bot interface {
  getGreeting(string, int) string
  // and many other functions
}


- interfaces are not generic types
- interfaces are implicit, matched with signature clauses only
- interfaces just help in maintaining types, no further validation



net/ http 
- - - - - - - - 

package main

import "fmt"
import "net/http"
import "os"

func main() {
  resp, err := http.Get("http://www.google.com")
  if nil != err {
    fmt.Println("Error: ", err)
    os.Exit(1)
  }
  fmt.Println("response: ",resp)
}

O/P
- - -

$ ./main 
response:  &{200 OK 200 HTTP/1.1 1 1 map[Cache-Control:[private, max-age=0] Content-Type:[text/html; charset=ISO-8859-1] Date:[Wed, 20 May 2020 21:41:29 GMT] Expires:[-1] P3p:[CP="This is not a P3P policy! See g.co/p3phelp for more info."] Server:[gws] Set-Cookie:[1P_JAR=2020-05-20-21; expires=Fri, 19-Jun-2020 21:41:29 GMT; path=/; domain=.google.com; Secure NID=204=V-0Lr33CGR1OSs2-eLQShrsFqM23NpD66sBXPp4lJky4VKQ1LSL6Uoz6SpRpUuDB8HToPfN6YFbT_cgGjnO595t07MExURpcb5UeC_juxvM0dMZrQduvG8FUJ5gsIzCNGCWumrxNWyrEhntEvhUBw9rfjkE9w6u7zzoq3_05ltI; expires=Thu, 19-Nov-2020 21:41:29 GMT; path=/; domain=.google.com; HttpOnly] X-Frame-Options:[SAMEORIGIN] X-Xss-Protection:[0]] 0xc00011e0e0 -1 [] false true map[] 0xc0001ee000 <nil>}



Some more info on Interfaces
- - - - - - - - - - - - - - - - - - - - - - - - -

Reader Interface - provides common implementation to read any source as byte slice - []byte, which can work with anything further

package main

import "fmt"
import "net/http"
import "os"

func main() {
  resp, err := http.Get("http://www.google.com")
  if nil != err {
    fmt.Println("Error: ", err)
    os.Exit(1)
  }
  // fmt.Println("response: ",resp)
  bs := make([]byte, 99999)
  resp.Body.Read(bs)
  fmt.Println("byte slice: ", bs)
  fmt.Println("byte slice as string: ", string(bs))

}


O/P
---
< Many byte characters and huge response >

Another option to print response -
...
io.Copy(os.Stdout, resp.Body)
...


Writer Interface - opposite of Reader interface, takes byte slice and converts into desired format

io.Copy(os.Stdout, resp.Body) --> the params are implementation of Writer interface and Reader interface respectively

Copy() internally uses CopyBuffer()


package main

import "fmt"
import "net/http"
import "os"
import "io"

type logWriter struct {}

func main() {
  resp, err := http.Get("http://www.google.com")
  if nil != err {
    fmt.Println("Error: ", err)
    os.Exit(1)
  }
  // fmt.Println("response: ",resp)

  // bs := make([]byte, 99999)
  // resp.Body.Read(bs)
  // fmt.Println("byte slice: ", bs)
  // fmt.Println("byte slice as string: ", string(bs))

  // io.Copy(os.Stdout, resp.Body)

  lw := logWriter{}
  io.Copy(lw, resp.Body)

}

func (logWriter) Write(bs []byte) (int, error) {
  // return 1, nil
  fmt.Println(string(bs))
  fmt.Println("length ---- ", len(bs))
  return len(bs), nil
}

o/p
- - - 

< response body >



Channels and Go Routines
- - - - - - - - - - - - - - - - - - - - - - -

Example Code:

main.go
- - - - 
package main

import "fmt"
import "net/http"

func main() {
  links := []string {
    "http://www.google.com",
    "http://www.golang.org",
    "http://www.stackoverflow.com",
    "http://www.amazon.com",
  }

  for _, link := range links {
    checkLink(link)
  }

}

func checkLink(link string) {
  _, err := http.Get(link)
  if nil != err {
    fmt.Println(link, " is down!!")
    return
  }
  fmt.Println(link, " is up!!")
}

O/P
- - -

$ ./main 
http://www.google.com  is up!!
http://www.golang.org  is up!!
http://www.stackoverflow.com  is up!!
http://www.amazon.com  is up!!



Note: As clear from the response, this is sequential and involves waiting. Now, need to see how to make the calls in parallel - Go Routines.

Go Routine lets the Main Routines knows, that there is a blocking function and Main should continue.

Syntax: just need to add go prefix against the function calls
go checkLink(link)

Go Scheduler takes care of the monitoring. Be default, it uses only one CPU core, but it can be customized to distribute the load.

Concurrency/ Parallelism can be achieved once multiple cores are involved.

Main routine doesn't give any priority to the child routines hence they are not executed. Therefore, need to use Channels.
It acts as a mediator to communicate between the Main routine and the child routines.
Channels are strictly defined per types, string channel can't support the int type.


How to send data with Channels

channel <- 9         : send the value 9 into channel
n <- channel         : waiting for channel to get the value and assigning it further to the variable n

fmt.Println(<- channel) : waiting for channel to get the value and hence printing it afterwards


Code:

main.go
- - - - - -
package main

import "fmt"
import "net/http"

func main() {
  links := []string {
    "http://www.google.com",
    "http://www.golang.org",
    "http://www.stackoverflow.com",
    "http://www.amazon.com",
  }

  c := make(chan string) // need to pass this channel in function call

  for _, link := range links {
    go checkLink(link, c)
  }
  fmt.Println(<- c)
}

func checkLink(link string, c chan string) {
  _, err := http.Get(link)
  if nil != err {
    fmt.Println(link, " is down!!")
    c <- "down !!"
    return
  }
  fmt.Println(link, " is up!!")
  c <- "up!!"
}


O/P
- - -
$ ./main 
http://www.google.com  is up!!
up!!


One issue - only one url is checked...

Because, Main Routine proceeds as soon as one of the child routines comes back.
Message receiving is also a blocking code.

Making the channel receiver again in Main routine -

...
  fmt.Println(<- c)
  fmt.Println(<- c)
...
O/P
- - -
$ ./main 
http://www.google.com  is up!!
up!!
http://www.stackoverflow.com  is up!!
up!!

All results can be seen if same number of receivers are created, but hangs if any additional is there !!

Quick hack -
...
  for i :=0; i< len(links); i++ {
    fmt.Println(<- c)
  }
...



How to repeat the Routines?
- - - - - - - - - - - - - - - - - - - - - - - 

Passing links instead of values, and handling at the receiver ends

package main

import "fmt"
import "net/http"

func main() {
  links := []string {
    "http://www.google.com",
    "http://www.golang.org",
    "http://www.stackoverflow.com",
    "http://www.amazon.com",
  }

  c := make(chan string) // need to pass this channel in function call

  for _, link := range links {
    go checkLink(link, c)
  }
  // fmt.Println(<- c)
  // fmt.Println(<- c)

  // for i :=0; i< len(links); i++ {
  //   fmt.Println(<- c)
  // }

  for {
    go checkLink(<- c, c)
  }

}

func checkLink(link string, c chan string) {
  _, err := http.Get(link)
  if nil != err {
    fmt.Println(link, " is down!!")
    // c <- "down !!"
    c <- link
    return
  }
  fmt.Println(link, " is up!!")
  // c <- "up!!"
  c <- link
}


O/P
- - - 
< Going on and on... forever>



Alternatively, making it more readable --
...
  for l := range c {
    go checkLink(l, c)
  }
...



How to add a pause in between the checks?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

Use Time package, sleep function
Avoid adding pauses in the main routines, it's a bad way - blocking and queuing 

Use function literals, i.e. unnamed functions

func main() {
  links := []string {
    "http://www.google.com",
    "http://www.golang.org",
    "http://www.stackoverflow.com",
    "http://www.amazon.com",
  }

  c := make(chan string) // need to pass this channel in function call

  for _, link := range links {
    go checkLink(link, c)
  }

  // Alternative syntax
  for l := range c {
    // time.Sleep(5*time.Second) // 5 seconds, bad way - blocking and queuing
    // go checkLink(l, c)
    go func() {  // function literals, like unnamed and anonymous functions
        time.Sleep(5*time.Second)
        go checkLink(l, c)
      }  ()
  }

}

Issue: l is not a trustworthy variable to monitor for the Main routine, as it's value might have changed.
Output can be like first run happening fine for all URLs but after that stuck with one url's copy. (Go being pass by copy)

Fix, pass l from the range -
...
  for l := range c {
    go func(link string) {  
        time.Sleep(5*time.Second)
        go checkLink(link, c)
      }  (l)
  }
...

O/P AS EXPECTED -
- - - - - - - - - 

$ ./main 
http://www.google.com  is up!!
http://www.stackoverflow.com  is up!!
http://www.amazon.com  is up!!
http://www.golang.org  is up!!
http://www.google.com  is up!!
http://www.stackoverflow.com  is up!!
http://www.amazon.com  is up!!
http://www.golang.org  is up!!
http://www.google.com  is up!!
http://www.stackoverflow.com  is up!!
http://www.amazon.com  is up!!
^C

No comments:

Post a Comment

Featured post

Oracle SQL Scheduled Jobs - An Interesting Approach

  Oracle SQL Scheduled Jobs A DB Scheduler is the best way to automate any backend database job. For instance, if you want to process the p...