An Introduction to Go (golang)

Sriharsha Sistalam

(@harry_sistalam)

What is Go?

Go is yet another open compiled, staticly typed open source programming language created at Google by Robert Griesemer, Rob Pike and Ken Thompson

How is it different?

C/C++ languages gets compiled to assembly, then to executable machine code.

Java first to bytecode, then to machine code (JVM)

Perl/Python, each line is interpreted one by one and gets executed.

Go programs are directly compiled to machine code (binaries)

How is it different?

//helloworld file
$cat helloworld.go
package main
import "fmt"
func main(){
fmt.Println("Hello world")

//build the code
$go build helloworld.go
$ls hellworld*
helloworld helloworld.go

//execute it
$./helloworld
Hello World

Why Go?

Go was created at Google to address various issue they were facing due to their sheer size and scale.

 

 

  • Network servers that do a lot of stuff concurrently
  • Languages like C/C++ are not designed keeping several things like concurreny, dependency mgmt
  • Does take multi core systems, cloud computing in consideration 

Google wanted something that can be fastly compiled, with less type heirarchical, fully garbage collected, mainly concurrent executable. So they went ahead and built Go 

Why Go?

"Go is an attempt to combine the ease of programming of an interpreted, dynamically typed language with the efficiency and safety of a statically typed, compiled language"

Hell world - Go

# package name
package main

# import all the standard/third party libraries here
# even from github directly
import "fmt"

# main execution
func main(){

  #use a function under fmt package to print to screen
  fmt.Println("Hello world")

}

Add numbers - Go

➜  ~  cat add.go
package main

import "fmt"

func main(){
	var a,b int = 5, 6  # explicit variable declaration 
	fmt.Println(a + b)

}
➜  ~  go build add.go
➜  ~  ./add
11

Features of Go

 

Built in concurrency 

Standard library modules

functions as first class citizens

 

All other features of normal language like functions, data types, packages, package management, compiling/build/test tools, version control.

Go concurrency 

Concurrency is built into the language and it is a first class citizen just like functions.

  • Go routines
  • Channels
  • Synchornization utils

Go Routines 

It is a function that is capable of running concurrently with other functions (within same process)

 

Goroutines are lightweight and we can easily create thousands of them, they are simillar to light weight processes of erlang

Goroutines/Threads 

  Threads Goroutines
Runtime Runs as OS threads They are multiplexed on OS threads
Memory Significant memory is used for each thread (~ 1MB) Less memory (~2KB) is used for each Goroutine
Setup/Tear down cost High since OS is involved They are created and managed by runtime, hence overhead is less
Switching costs Context switch will result in saving of various register data, pretty significant if switching happens often  
  GP registers, Stack prointer, Program counter, Segments registers Program counter, Stack pointer, DX

Go Routines examples

In Go, routines are created by calling a function prefixed by 'go'

// A _goroutine_ is a lightweight thread of execution.

package main

import "fmt"

func f(from string) {
    for i := 0; i < 3; i++ {
        fmt.Println(from, ":", i)
    }
}

func main() {

    // synchronously.
    f("direct")

    // concurrently
    go f("goroutine")

    // You can also start a goroutine for an anonymous
    // function call.
    go func(msg string) {
        fmt.Println(msg)
    }("going")

    // Our two function calls are running asynchronously in
    // separate goroutines now, so execution falls through
    // to here. This `Scanln` code requires we press a key
    // before the program exits.
    var input string
    fmt.Scanln(&input)
    fmt.Println("done")
}

Go Channels 

Channels are used as communication medium between two or more goroutines, they provide a way for synchronizing the execution of different parts of system.

They are analogous to unix pipes which provides the glue between multiple programs

Go Channels 

package main

import "fmt"
import "time"

// This is the function we'll run in a goroutine. The
// `done` channel will be used to notify another
// goroutine that this function's work is done.
func worker(done chan string) {
    fmt.Print("working...")
    time.Sleep(time.Second)
    fmt.Println("done")

    // Send a value to notify that we're done.
    done <- "Function is done"
}

func main() {

    // Start a worker goroutine, giving it the channel to
    // notify on.
    done := make(chan string, 1)
    go worker(done)

    // Block until we receive a notification from the
    // worker on the channel.
    fmt.Println(<-done)
}

Various types of channels like inbound, outbound, multi plexed etc exist.  Values of any type can be communicated through this channel.

Go concurrency CSP

Communicating sequential processes is the standard design on which many concurrent languages are designed

"Do not communicate by sharing, share by communicating"

Usual way of multi threading by shared memory

CSP suggested way of handling without shared memory (wrt golang)

Go concurrency example

Have two threads/components/routines/functions which produces a sequence of numbers and a main function which consumes these numbers concurrently

Go implementation

//this produces even numbers
func printEvenNumbers(ch chan int){
  for i:=2; i<=45; i=i+2 {
   ch<-i;
  }
  close(ch);
}

//this produces odd numbers
func printOddNumbers(ch chan int){
  for i:=1; i<=25; i=i+2 {
   ch<-i;
  }
  close(ch);
}

//main function which consumes all numbers
func main() {
 var a = make(chan int);
 var b = make(chan int);

 go printOddNumbers(a);
 go printEvenNumbers(b);
 
 var c = merge_alternate(a, b)

 for i:= range c {
  fmt.Println(i);
 }
}

Java implementation

//main function
Thread t1 = new Thread(new Counter(numbers, false));
Thread t2 = new Thread(new Counter(numbers, true));
try {
    t1.start(), t2.start();
    t1.join(), t2.join();
    System.out.println(numbers);
} catch (Exception e) {
	   System.exit(1);
}

class Counter extends Thread {
        static List<Integer> numbers; boolean even;
        //constructor
        public void run(){
            int itr = this.even ? 2: 1;
            System.out.println("Starting " + itr);
            for (; itr < 53; itr = itr + 2) {
                    synchronized (numbers) {
			numbers.add(itr);
                        try {
			    numbers.notify();
                            numbers.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
	   System.out.println("Done in run");
        }
}

Go Sync options

Select: It lets you wait on selected channel operations.

// _Timeouts_ are important for programs that connect to
// external resources or that otherwise need to bound
// execution time. Implementing timeouts in Go is easy and
// elegant thanks to channels and `select`.

package main

import "time"
import "fmt"

func main() {

    // For our example, suppose we're executing an external
    // call that returns its result on a channel `c1`
    // after 2s.
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()

    // Here's the `select` implementing a timeout.
    // `res := <-c1` awaits the result and `<-Time.After`
    // awaits a value to be sent after the timeout of
    // 1s. Since `select` proceeds with the first
    // receive that's ready, we'll take the timeout case
    // if the operation takes more than the allowed 1s.
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
}

Go Concurrency examples

Implementing a thread pool of process tasks

package main
import "fmt"
import "time"

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Println("worker", id, "processing job", j)
        time.Sleep(time.Second)
        results <- j * 2
    }
}

func main() {

    jobs := make(chan int, 100)
    results := make(chan int, 100)

    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    for j := 1; j <= 9; j++ {
        jobs <- j
    }

    close(jobs)

    for a := 1; a <= 9; a++ {
        <-results
    }

Go Concurrency examples

Dining philosophers

type Philosopher struct {
	name      string
	chopstick chan bool
	neighbor  *Philosopher
}

func (phil *Philosopher) getChopsticks() {
	timeout := make(chan bool, 1)
	go func() { time.Sleep(1e9); timeout <- true }()
	<-phil.chopstick
	fmt.Printf("%v got his chopstick.\n", phil.name)
	select {
	case <-phil.neighbor.chopstick:
		fmt.Printf("%v got %v's chopstick.\n", phil.name, phil.neighbor.name)
		fmt.Printf("%v has two chopsticks.\n", phil.name)
		return
	case <-timeout:
		phil.chopstick <- true
		phil.think()
		phil.getChopsticks()
	}
}

What is Go good for?

Go is really good at handling things for "scale" which requires both high throughput as well as high concurrency.

  • For distributed applications like (deployment systems, caching servers, service orchestration)
  • Network and web servers (load balancing servers, video servers etc)
  • General web APIs and servers.
  • Standalone command line utilities

What is Go good for?

Text

TCP programming, sockets (Websockets)

Programs that involves distribution of work across workers (nginx)

Better than Node.js?

Yes, users are mainly shifting out of Nodejs to Go for below reasons

  • Nodejs is not ideal for CPU intensive tasks
  • Error handling is notoriously difficult and hard (single threaded)
  • Can achieve same throughput with concurrency without the burden of Single threaded env.

How to get started?

Who all are using it?

Google (of course)

CoreOS

Docker

Dropbox

Uber

Github

Netflix

SpaceX

many many more..

References:

Thank you

Introduction to Go

By Sriharsha Sistalam

Introduction to Go

  • 838