Introduction to Golang
Basics
Packages
- Programs start running with package main
package main
import "fmt"
func main(){
fmt.Println("Hello World")
}
- Multiple package import paths.
package main
import (
"fmt"
"math/rand"
)
func main(){
fmt.Println("Fav number:", rand.Intn(10))
}
- Exported Names
Exported names start with capital letter,just like
Pi
inmath
package.
package main
import (
"fmt"
"math"
)
func main(){
fmt.Println(math.Pi)
}
Functions
- Functions can take zero or more arguments, variable type comes after variable name.
package main
import "fmt"
func multiply(x int,y int) int {
return x*y
}
func main(){
fmt.Println(multiply(4,2))
}
- Shortening function parameters,for better readability.
func multiply(x,y int) int {
return x*y
}
- Return multiple results in a function definition
func addsub(x,y int) (int,int){
return x+y,x-y)
}
func main(){
fmt.Println(addsub(20,20))
}
- Named return values.
func addsub(x,y int) (add,sub int) {
add = x+y
sub = x-y
return
}
- Multiple Returns
func swap(x,y string) (string,string){
return y,x
}
func addsub(x,y int) (int,int){
return x+y,x-y
}
func main(){
fmt.Println(swap("hello","world")
fmt.Pritln(addsub(10,20))
}
- Variables
package main import "fmt" var hello bool var i int=0 func main(){ var b,c int= 20,30 fmt.Println(i,hello,b,c) }
Short variable description
k:=3
Types
- Basic Types
- Integers - Signed (int,int8,int16,int32,int64) Unsigned (uint,uint8,uint16,uint32,uint64)
- Floats - float32,float64
- Complex Numbers - float32,float64
- Byte
- Rune
- String
- Rune
- Boolean
- Composite Types
- Collections/Aggregation - Arrays,Structs
- Reference Types - slices, maps,channels, pointers,functionos
- Interface
package main
import (
"fmt"
"math/cmplx"
)
var (
hello bool = false
name string = "vinay"
age int = 22
z complex128 = cmplx.Sqrt(-5+2i)
)
func main(){
fmt.Printf("Type: %T Value: %v\n",hello,hello)
fmt.Printf("Type: %T Value: %v",name,name)
fmt.Printf("Type: %T Value: %v",age,age)
fmt.Printf("Type: %T Values: %v",z,z)
}
//observe the printf not the usual Println
Type conversion & Inference
package main
import "fmt"
import "math"
// The expression T(v) converts the value v to the type T.
func main(){
// Type conversion
var x,y int =3,5
var f float64 = math.Sqrt(float64(x*x + y*y))
var z uint = uint(f)
fmt.Println(x,y,z)
fmt.Printf("Type: %T Value: %v\n",x,x)
fmt.Printf("Type: %T Value: %v\n",z,z)
fmt.Printf("Type: %T Value: %v\n",f,f)
// Type Inference - the variable's type is inferred without from the value
// on the right side
i:=22
fmt.Printf("Type: %T Values: %v",i,i)
}
Constants
package main
import "fmt"
func main(){
const Pi = 3.14
// never use short description for constants i.e :=
fmt.Println(Pi)
}
#### Primitive Data types:
byte: uint8 ( byte is an alias for uint8)
rune: int32 ( rune is an alias for int32)
go constants
```go
const n = 10000
fmt.Println(n)
Loops
Loop: for loop is every loop can be used as infinite loop,while loop etc in GOLANG
-
Machine takes care of go formatting ( gofmt)
-
Statement Termination in Golang: go uses semicolon for statement termination, where a simple lexer rule is added which does not add them to source, where the rule says if a new line appears after the end of the token of the statement, then insert a semicolon, this is a lexer rule and this is the reason why
{}
inif
andfor
are immediately put after the statement. -
Function return values: Golang returns multiple values.
-
Blank Identifier in golang where the value is not required throughout the program, we dont need to create a dummy variable if _,err := os.Stat(path); os.isNotExist(err)
-
Strings are Immutable in Golang
-
IOTA used to create incrementing constants in golang, inserting a blank/new line in const block will not increase the iota value, using the blank identifier( _) within the const block will increase the iota incrementing value.
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
_
Someday
Bluemoonday
)
The incrementing value for Sunday
constant is 0, for Monday
is 1 and so on, the incrementing value for Someday
is 8 because _
the blank identifier also increases the iota value.
- Scoping Rules : Variables are declared in two scopes namely Global scope and Local scope
func main() {
fmt.Println("Hello, 世界")
i := 10
for i := 0; i < 5; i++ {
fmt.Println(i)
}
fmt.Println(i)
}
the first i
variable value will be 10, in the for loop block another local variable i
is created without affecting the initially declared variable.
Arrays
var arr [4]int
fmt.Println(arr)
var arr1 [4]int
arr1 = [4]int{0, 1, 2, 3}
fmt.Println(arr1)
arr2 := [4]int{0, 1, 2, 3}
fmt.Println(arr2)
In Arrays we cannot extend the size once data is assigned to them, need to create a larger array size.
- Slices unlike arrays doesn’t have fixed size, they are dynamic in nature. An uninitialized slice equals to nil and has size 0.
//slices
var sl []int
fmt.Println(sl == nil)
// slices are created using the make function which has the syntax make(type,size,capacity)
sl1 := make([]int, 4) // where 4 is the size
fmt.Println(sl1 == nil)
sl2 := []int{0, 1, 2, 3, 5, 6, 6, 76, 7, 7}
fmt.Println(sl2)
fmt.Println("Size of the Slice sl2:", len(sl2))
sl2[3] = 100
fmt.Println("After the change of value at index 3", sl2[3])
//Extending the size of the slice using append function
sl2 = append(sl2, 20000)
// two slices can be copied too
sl4 := make([]int, len(sl1))
copy(sl2, sl4)
fmt.Println("Copied slice using the copy() function")
//slices has operator support, slice[low:high] just like python
slice5 := sl2[5:9]
fmt.Println("Sliced Slice", slice5)
fmt.Println("Sliced slice till index 5", sl2[:5])
Ellipsis (…) in Golang are used in variadic function, array literals and operation etc
Maps
Maps are like hashes or dictionary which provides key value pairs for data for unordered data collection
//creating a map using make function
var prodPrice map[string]int
fmt.Println(prodPrice)
tempPrice := make(map[string]int) // where string is the key and int is the value of the map
tempPrice["tomato"] = 200
fmt.Println("Temporary Price", tempPrice)
prodPrice = tempPrice
fmt.Println("Product Price", prodPrice)
//declaring and initialising the map using map literal
empSalary := map[string]int{
"vinay": 100,
"vinutha": 200,
}
empSalary["mahesh"] = 300
fmt.Println("Employee Salary List:", empSalary)
fmt.Println(empSalary["vinay"])
vinaySalary, ok := empSalary["vinay"]
fmt.Println(vinaySalary, ok)
//fetching the value of non existent key
dineshSalary, ok := empSalary["dinesh"]
fmt.Println(dineshSalary, ok)
fmt.Println("Length of the map Employe Salary:", len(empSalary))
// deleting a key,value pair from the map
delete(empSalary, "vinay")
vinaySalary, ok = empSalary["vinay"]
fmt.Println(vinaySalary, ok)
fmt.Println("Length of the map Employe Salary after deletion:", len(empSalary))
Pointers
Pointers, &(ampersand) is used for address and asterisk for value, allows you to pass references in golang
val := 20
var ptr *int = &val
fmt.Println(ptr)
fmt.Println(*ptr)
var ptr1 *int = new(int)
fmt.Println(ptr1, *ptr1)
*ptr1 = 20
fmt.Println(ptr1, *ptr1)
value := 200
valptr := &value
fmt.Println(valptr, *valptr)
Defer
Defers the execution of the function.Arguments evaluated immediately.Function called only after parent function is returned.
defer fmt.Println("second")
fmt.Println("first")
Labels in Golang
i := 5
outerlabel:
if i > 0 {
fmt.Println(i)
i--
goto outerlabel
}
fmt.Println("Labels and Looping")
Looping over collections
Usually collections are looped using for loop traversing each element. initializing the variable and setting a condition, and the update condition.
Using range keyword for collection traversal.
//slice collection traversal
sl:= []int{10,20,30,40}
for i,value : range sl {
fmt.Println(i, sl[i])
}
Looping over Map collection
empSalary := map[string]int{
"vinay": 200,
"vinutha": 300,
"mahesh": 400,
}
for key, value := range empSalary {
fmt.Println(key, value)
}
use blank identifier in place of key, in case if only value is needed
strings
fmt.Println(strings.Contains("Hello world", "world"))
fmt.Println(strings.Replace("Hello World World", "World", "Wooooorld", 2)) // where both the occurences are replaced.
fmt.Println(strings.Title("hello world")) //capitilize - captilizes the first letter.
fmt.Println(strings.Trim("_____Hello __ hi _ World___", "_"))
// single return type
func add(a, b int) int {
return a + b
}
func addThreeNumbers(a, b, c int) int {
return a + b + c
}
func main() {
fmt.Println(add(2, 4))
fmt.Println(addThreeNumbers(2, 12, 54))
fmt.Println(addAndSubtract(10, 20))
}
// multiple return types
func addAndSubtract(a, b int) (int, int) { // function signature
return a + b, a - b
}
If the function signature consists of return parameters the body should have the keyword return and return values
Multiple return types are returned from the function which also return result along with error values from a function
Object Orientation
Objects which has both state and behaviour, using the struct type which has named field with methods binded to it.
type Cube struct {
depth float64
width float64
length float64
}
func (c *Cube) volume() float64 {
return c.depth * c.width * c.length
}
func main() {
c := Cube{depth: 4, width: 4, length: 4}
fmt.Println("volume", c.volume())
}
Achieving Inheritance using embedded types.
```golang
type Part struct {
Manufacturer string
}
type Tyre struct {
Part Part
}
func (part *Part) Mfc() string {
return part.Manufacturer
}
func main() {
t := Tire{Part{Manufacturer: "Mahindra"}}
fmt.Println(t.Part.Mfc())
}
Inheritance using embedded struct with anonmyous types
type Part struct {
Manufacturer string
}
type Tire struct {
Part
}
func (part *Part) Mfc() string {
return part.Manufacturer
}
func main() {
t := Tire{Part{Manufacturer: "Mahindra"}}
//fmt.Println(t.Part.Mfc())
fmt.Println(t.Mfc())
}
Variadic Function
A function which can be called with any number of trailing arguments, fmt.Println() is a common type of variadic function.
range keyword returns index,item when called on an array or slice
for index, item := range numbers {
fmt.Printf("numbers[%d] = %d\n", index, item)
}
func sum(nums ...int) {
fmt.Print(nums, " ")
total := 0
for _, num := range nums {
total += num
}
fmt.Println(total)
}
func main() {
sum(1, 2, 3)
sum(1, 2, 4, 5)
slice := []int{34, 5, 6, 7, 78} //variadic function can be called with individual arguments by adding ellipses to the slice
sum(slice...)
}
Anonymous Functions
In Go functions without name is called anonymous function, just works like a normal function in golang.
In Anonymous function what we do is call the anonymous function to the variable and then use the variable to call the function, like greet() in the below code example.
func main() {
var greet = func() {
fmt.Println("Hello world!! greeting the world")
}
greet()
}
`go vet` command is used to check for the source code and reports syntax error and bugs
Anonymous function with parameters
func main() {
var greet = func() {
fmt.Println("Hello world!! greeting the world")
}
greet()
var total = func(n1, n2 int) int {
fmt.Println(n1 + n2)
}
total(10, 20)
}
Go Closure
go closure is a nested function that allows us to access variables of the outer function, even after the outer function is closed.
Returning a function in golang
func return_function_in_function() func() {
return func() {
fmt.Println("hello world")
}
}
func main() {
function1 := return_function_in_function()
function1()
}
Go closure is a nested function with return type for the inner nested function
// go closure
func greet() func() string {
name := "John"
return func() string {
name = "Hi " + name
return name
}
}
func main() {
message := greet()
fmt.Println(message())
}
Structs
go’s structs are collection of named fields, they are useful for grouping to form records
- Omitted fields will be zero valued.
type Box struct {
L float64
B float64
H float64
}
func main() {
b := Box{10, 20, 30}
fmt.Println(b)
fmt.Println(b.L, b.B)
}
-
Values of the struct variables can be updated with the dot operator, making the structs mutable which means it can be updated.
-
If a struct is single valued, we dont have to give it a name, it can be anonymous struct type
jimmy_the_dog := struct {
name string
age int
}{
"Jimmy",
20,
}
fmt.Println(jimmy_the_dog)
anonymous struct type is used for table driven tests.
Interfaces
Interfaces are named collection of method signatures. All methods has to be implemented.
type Food interface {
Nutrition() string
Carbohydrates() int
}
type Burger struct {
}
type RagiBall struct {
}
func (b Burger) Nutrition() string {
return "Burger is not nutritious"
}
func (b Burger) Carbohydrates() int {
return 200
}
func (r RagiBall) Nutrition() string {
return "Ragiball is nutritous"
}
func (r RagiBall) Carbohydrates() int {
return 1000
}
// the methods of Food interface are implemented for both Burger and Ragiball
func main() {
foods := []Food{Burger{}, RagiBall{}}
for _, f := range foods {
fmt.Println(f.Nutrition(), f.Carbohydrates())
}
}
Error and Exceptions
- Compile Time Errors: Syntax errors, Semantic Errors, Lexical Errors, logical errors
- Runtime errors:
- Signal Abort: a signal sent to abort the program.
- Non zero exit code: this error is raised when the main program does not return zero exit code.
- Segmentation Fault: when program tries to access memory which is access restricted.
- Floating Point exception: division by zero
Normal flow of the program is not disturbed.
Errors in go are values, go values Represented with builtin error type. Functions return error values.
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("myFile.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(file.Name())
}
open myFile.txt: no such file or directory
Error type: error is an interface type with one method
type error interface{
Error() string
}
To define a custom error, we can use the function errors.New() from the error package which takes only one string argument.
import (
"errors"
"fmt"
)
func main() {
r := 0
if r <= 0 {
fmt.Println(errors.New("r is less than or equal to zero"))
}
}
Go’s way to handle exception using defer panic and recover GO does not have Exceptions like java and c++, defer panic and recover can be used. Avoid try catch,finally complexity Defer panic and recover only in exceptional situations
- Defer : defer places a function call onto a defer stack and executed in reverse order,it is usually used for cleanup actions
- Panic: is a normal function that terminates the normal flow of execution of the program,calling function does not know how to handle them, so the program is terminated.
- Recover: mechanism to recover from terminating.
Handling errors using errors package.
package main
import (
"errors"
"log"
"os"
)
func main() {
if _, err := os.Open("myFile.txt"); err != nil {
if errors.Is(err, os.ErrNotExist) {
log.Println("File does not exist")
} else {
log.Println(err)
}
}
}
the above program handles file not found using errors.Is method of ErrNotExist
Panic: is an ordinary function which stops the normal flow of the program and begins panicking. When function calls the panic function the normal flow of the execution of the program is stopped and deferred functions are executed normally and then the control is given back to the normal function.
Panics can be initiated by invoking panic directly. They can also be caused by runtime errors, such as out-of-bounds array accesses.
package main
import "fmt"
func division(num1, num2 int) {
// if num2 is 0, program is terminated due to panic
if num2 == 0 {
panic("Cannot divide a number by zero")
} else {
result := num1 / num2
fmt.Println("Result: ", result)
}
}
func main() {
division(4, 2)
division(8, 0)
division(2, 8)
}
Result: 2
panic: Cannot divide a number by zero
goroutine 1 [running]:
main.division(0x547258?, 0xc0000061c0?)
/tmp/sandbox1149350607/prog.go:9 +0x9e
main.main()
/tmp/sandbox1149350607/prog.go:19 +0x29
Program exited.
Recover: Sometime when panic is called the program is terminated but no value is returnedsometimes it might be important for a program to complete its execution and get some required results.
package main
import "fmt"
func handlePanic() {
a := recover()
if a != nil {
fmt.Println("RECOVER", a)
}
}
func division(num1, num2 int) {
defer handlePanic()
// if num2 is 0, program is terminated due to panic
if num2 == 0 {
panic("Cannot divide a number by zero")
} else {
result := num1 / num2
fmt.Println("Result: ", result)
}
}
func main() {
division(4, 2)
division(8, 0)
division(2, 8)
}
output
Result: 2
RECOVER Cannot divide a number by zero
Result: 0
Program exited.
Creating a file using os.Create()
func main() {
f, err := os.Create("create.txt")
defer f.Close()
if err != nil {
log.Fatal(err)
}
fmt.Println(f)
}
Creating and opening the file using os package
import (
"fmt"
"log"
"os"
)
// open and close file
func closer(f *os.File) error {
f.Close()
fmt.Println(f.Name(), "successfully closed")
return nil
}
func main() {
f, err := os.Create("create.txt")
f, err = os.Open("create.txt")
defer closer(f)
if err != nil {
log.Fatal(err)
}
fmt.Println("File successfully opened", f.Name())
}
Creating and deleting a file
// create and delete a file
func main() {
f, err := os.Create("create.txt")
err = os.Remove("create.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("File Deleted successfully", f.Name())
}
Copying the file
func main() {
src, err := os.Create("src.txt")
src, err = os.Open("src.txt")
defer src.Close()
if err != nil {
log.Fatal(err)
}
dst, err := os.OpenFile("dst.txt", os.O_RDWR|os.O_CREATE, 0755)
defer dst.Close()
if err != nil {
log.Fatal(err)
}
w, err := io.Copy(dst, src)
if err != nil {
log.Fatal(err)
}
fmt.Println(reflect.TypeOf(w))
fmt.Println(w)
}
os.Rename(oldPath,newPath) to rename or move the file - also supports relative paths, not only absolute paths.
Truncating a file
func main() {
f, err := os.Open("myFile.txt")
err := os.Truncate("myFile.txt", 10) // truncated to 10 bytes
fmt.Println(f)
}
FileInfo
func main() {
f, _ := os.Open("myFile.txt")
f, err = os.Stat("myFile.txt")
if err != nil {
log.Fatal(err)
}
log.Println("File Name", f.Name())
log.Println("File Size", f.Size())
log.Println("File Permissions", f.Mode())
}
Reading files into memory
import (
"fmt"
"log"
"os"
)
func main() {
os.Create("myFile.txt")
// add contents or text to file created
contents, err := os.ReadFile("myFile.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("Contents are sliced to string :", string(contents))
}
Reading files line by line
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
os.WriteFile("file.txt", []byte("Hello World \nNew Line hello world"), 0666)
f, err := os.Open("file.txt")
os.Open("file.txt")
defer f.Close()
if err != nil {
log.Fatal(err)
}
s := bufio.NewScanner(f)
for s.Scan() {
fmt.Println(s.Text())
}
if err := s.Err(); err != nil {
log.Fatal(err)
}
}
writing to a file using os.Writefile and opening the file and using NewScanner using the bufio package.
Directory IO operations
Creating a directory
import (
"errors"
"fmt"
"log"
"os"
)
func main() {
// Stat() returns the fileinfo
d, err := os.Stat("subdir")
fmt.Println("Error returned by os.Stat() is ", err)
if err == nil {
log.Fatal(d.Mode(), d.IsDir())
log.Fatal("file/directory name already exists")
}
if errors.Is(err, os.ErrNotExist) {
err := os.Mkdir("subdir", 0777)
if err != nil {
log.Fatal(err)
}
fmt.Println(" Directory created")
}
}
Creating nested directories !!!
import (
"fmt"
"log"
"os"
"path/filepath"
)
func main() {
// creating nested directories
p := filepath.Join("../test", "subdir1", "subdir2")
err := os.MkdirAll(p, 0777)
if err != nil {
log.Fatal(err)
}
fmt.Println(p, "nested file directories created")
}
Go Packages
Go Programs are composed of one or more packages, Packages organize code in such a way that make the code reusable and easy to read
DRY - Dont Repeat Yourself | Reuse code
package <package_name> to name a package
To use the package we use import.
Two types of packages, executable package and utility package, main is the executable package and utility packages are called from main
Purpose of go packages: Modularity, maintainability, Reusability Import path to a repository, aliases and packages can be nested too.
Sharing Packages: - Can be kept private - Make members available to other packages. - Packages can use exported members
Captilization and Exporting If it begins with a lowercase letter, it won’t be exported, if its not exported it is only visible inside the package
Naming Packages and Package convention
go mod init example.local/calc
main.go package main
import "fmt"
import "example.local/calc/math"
import "example.local/calc/math/stats"
func main(){
fmt.Println("Hello world")
fmt.Println(math.Add(10,20))
vals := []float64{10.0,20.0,30.0}
fmt.Println(stats.Avg(vals))
}
math.go
package math
func Add(x,y int) int {
return x+y
}
func Sub(x,y int) int {
return x-y
}
stats.go
package stats
func Avg(vals []float64) float64 {
total := 0.0
for _,val := range vals {
total += val
}
return total/float64(len(vals))
}
where stats is nested within the math
directory and the package name is example.local/calc
consisting of two directories
Package declaration the accessing line is the directory name, even the package is separated the files should be within the same package.
Blank Import: add underscore() before the package like ( “strings”) where string is a blank import, blank import is needed when a database driver may be loaded without the need for the importing program.
Alternative import methods
import (
"fmt"
"log"
m "example.local/calc"
}
import (
f "fmt"
)
func main() {
f.Println("Hello World, with alternative ways to import")
}
Documenting Custom Packages
go source documentation comments: a doc-comment before the exported members is the documentation.
$go doc example.local/calc/math package math // import “example.local/calc/math”
func Add(x, y int) int func Sub(x, y int) int
$ godoc -http=localhost:8080 starts an http server for the documentation of the code.
Documentation with doc.go within the package
Module is go support for dependency managament, a module by defintion is a collection of related pacakages. go.mod contains the following: - the dependency packages for successfull build - golang version used for the package - module import path
go.sum This list file downloads the checksums of the direct and indirect dependencies required along with version, The checksum present in go.sum is used to validate the checksum of each direct and indirect dependency to confirm that none has been modified.
Direct -A direct dependency is a dependency which the module directly imports.
Indirect – It is the dependency that is imported by the module’s direct dependencies. Also, any dependency that is mentioned in the go.mod file but not imported in any of the source files of the module is also treated as an indirect dependency.
Init function in Golang
Init function allows initialization of a package, init function takes no argument neither returns any. Multiple init functions can be defined in a file and will be executed one after another maintaining the order.
Init functions is more suited for initialization tasks of the package.
package main
import (
"fmt"
)
func init() {
fmt.Println("Runs first")
}
func main() {
fmt.Println("Hello, World")
// Runs first
// Hello, World
}
package main
import (
"fmt"
)
func init() {
fmt.Println("Runs first")
}
func init() {
fmt.Println("Runs second")
}
func main() {
fmt.Println("Hello, World")
// output:
// Runs first
// Runs second
// Runs last
// Hello, World
}
func init() {
fmt.Println("Runs last")
}
Sequential Programming Concepts
Concurrency is a critical component of modern software as it leverages modern hardware devices. Concurrent applications can execute various parts of the program, out of sequence without effecting the final outcome, which is not similar to parallelism where multiple programs are run.
Sequential Programs: are a set of same sequence of instructions which will produce consistent results.
Single vs MultiThreading Programming,
A thread of instruction is the smallest sequence of instruction, independently managed by schedule Single Thread: single command processed.
In Multithreading, threads get executed concurrently, long running tasks are distributed among multiple threads, leveraging hardware
Multithreaded Process: 1. User Level Threads - smaller in size
- Kernel Level Threads: light weight threads managed by operating system.
Concurrency: a program making more progress, working on one or more tasks running at the same time. The CPU time is split across multiple tasks, making small amount of progress of the running tasks.
Concurrency refers to or more actions in progress at the same time.
Parallelism: Application tasks are split into smaller subtasks, these smaller subtasks are processed in parallel. Parallelism refers to two or more actions executing simultaneously.
Concurrency handles tasks, whereas parallelism performs the task.
In Concurrency tasks execute concurrently, these concurrent tasks may interact with each other, which may end at same time.
Levels of Concurrency:
Low Levels Concurrency: Thread safety synchronization tasks, atomic operations Mid Level Concurrency: Explicit locks are used. High Level Concurrency: Go routine, light weight thread,
Advantages: Performance, Scalability, Fault Tolerance, Well crafted code.
Go Routines
Go Routines are lightweight threads, managed by go runtime which makes them lighter than operating system threads.
Go Routines are asynchronous parallel programs, which execute faster than sequential tasks.
- Go Routines run independently,
- To create a go routine, just add the keyword
go
before the function name
Go uses own scheduler to map small number of threads to the operating system thread. By mapping many go rountine to a single os thread, we can create and destroy thread in our application without worrying about the performance impact. Highly efficient and performant.
Channels
If we want two go routines to communicate to each other. Channels provide communication channel between go routines. Go routines send and receive data using channels and are allocated using the built in function make.
Referenced using an left arrow mark.
myChannel := make(chan int,100) myChannel <-10
the above code creates a buffered channel, of the size provided is allocated, here creates an buffered channel size of 100.
myChannel<-10
: puts an integer value 10 into the buffered channel.
Go Routines are the smallest unit of execution in go programs
- Small stack size, Java uses 1MB as stack size, where as go starts with 2KB
- Channels are main method for sending and receving data bw go routines
- In channels send and receive operations are blocking operations which notifies about the state of the go routine.
- Channels can be unidirectional or bidirectional,
Waitgroups
When many go routines are mapped to the OS thread, but if the OS thread is terminated when there are few go routines near to completion of execution, which leads to incompletion to over come this we have, waitgroups
- Ensures goroutines runs to completion
- Wait for completion of multiple goroutines.
Waitgroups can be used to block the OS thread until all the go routine’s execution is completed. waitgroup is a group struct in sync package of golang.
Select Statements
Somewhat similar to switch case statement but for channels. Atomic Layer operations are implemented in the low layer which utilize CPU instructions for synchronization. Atomic operations cannnot be interupted, synchronize read write access using multiple goroutines.
Belong to sync/atomic package that can be used for low level communication.