Golang concurrency

G

Concurrent computing is a form of computing in which several computations are executed during overlapping time periods—concurrently—instead of sequentially (one completing before the next starts). This is a property of a system—this may be an individual program, a computer, or a network—and there is a separate execution point or “thread of control” for each computation (“process”). A concurrent system is one where a computation can advance without waiting for all other computations to complete.

(source: https://en.wikipedia.org/wiki/Concurrent_computing)

If the above paragraph confuses you, don’t worry. I don’t understand it either. lol

So let’s learn concurrency without even knowing what it is? Yes, you heard me correctly. No pre-requisite. No threads, no process etc

Here’s a basic go code:

package main

import "fmt"


func print(s string){
	fmt.Println(s)
}

func main(){
	print("hi")
	print("hello")
}

Nothing fancy here. But I heard if you add time.sleep() you can actually stop that line of code for any hours of time. Magic!

So check out this code now.

package main

import (
	"fmt"
	"time"
)

func print(s string) {
	fmt.Println(s)
	time.Sleep(time.Second * 2)
}

func main() {
	print("hi")
	print("hello")
}

It’s useless to show just the output here. Run it for yourselves.

See theres a delay between each print() statement. Now lets spice things up.

package main

import (
	"fmt"
	"time"
)

func print(s string) {
	for i := 1; i <= 5; i++ {
		fmt.Println(s)
		time.Sleep(time.Second)
	}

}

func main() {
	print("hi")
	print("hello")
}

Now it should have taken atleast 10sec to execute which is ok. We’re the one who coded this.

But can we do better? Sure I want a delay. But while waiting maybe try to execute the next line. and then come back.

Here comes the goroutine. Just add go to the function.

package main

import (
	"fmt"
	"time"
)

func print(s string) {
	for i := 1; i <= 5; i++ {
		fmt.Println(s)
		time.Sleep(time.Second)
	}

}

func main() {
	go print("hi")
	print("hello")
}

Now it runs concurrently.

But what if I tried to make both the functions go

package main

import (
	"fmt"
	"time"
)

func print(s string) {
	for i := 1; i <= 5; i++ {
		fmt.Println(s)
		time.Sleep(time.Second)
	}

}

func main() {
	go print("hi")
	go print("hello")
}

This happens because our code is non-blocking in nature. Here we don’t wait for both the hi and hello and the there’s no statement after that.So the program terminated.

How to solve this?

maybe add a sleep() at the end of the statement.

package main

import (
	"fmt"
	"time"
)

func print(s string) {
	for i := 1; i <= 5; i++ {
		fmt.Println(s)
		time.Sleep(time.Second)
	}

}

func main() {
	go print("hi")
	go print("hello")

	time.Sleep(time.Second * 5)
}

Ah! that trick worked. But we’re talking a wild guess here. For example see this:

package main

import (
	"fmt"
	"time"
)

func print(s string) {
	for i := 1; i <= 5; i++ {
		fmt.Println(s)
		time.Sleep(time.Second)
	}

}

func main() {
	go print("hi")
	go print("hello")

	time.Sleep(time.Second)
}

Hard coding the time value is a bad thing to do. How to actually solve this problem?

wait groups

With wait groups we can achieve this functionality.

First look at this code:

package main

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

var wg sync.WaitGroup

func print(s string) {
	for i := 1; i <= 5; i++ {
		fmt.Println(s)
		time.Sleep(time.Second)
	}
	wg.Done()
}

func main() {
	wg.Add(1)
	go print("hi")
	wg.Add(1)
	go print("hello")
	wg.Wait()
	
}

It worked! So what I did was

  1. used Add() to add it to wg
  2. used Done() to mark it as done
  3. used wait() to wait for those wg to finish executing.

defer

We managed to make it work. But what if an error occured in the forloop?

The program gonna wait for infinitely.

Bad. Thats bad.

How to solve this? Use Defer

package main

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

var wg sync.WaitGroup

func print(s string) {

	defer wg.Done()
	for i := 1; i <= 5; i++ {
		fmt.Println(s)
		time.Sleep(time.Second)
	}

}

func main() {
	wg.Add(1)
	go print("hi")
	wg.Add(1)
	go print("hello")
	wg.Wait()

}

I marked wg.Done() as defer. Basically when you marked any function as defer, go will run that function atlast.

For example see this:

package main

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

var wg sync.WaitGroup

func imbored(s string){
	fmt.Printf("------%s finished ----\n", s)
	wg.Done()
}

func print(s string) {

	defer imbored(s)
	for i := 1; i <= 5; i++ {
		fmt.Println(s)
		time.Sleep(time.Second)
	}

}

func main() {
	wg.Add(1)
	go print("hi")
	wg.Add(1)
	go print("hello")
	wg.Wait()

}

panic and recover

package main

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

var wg sync.WaitGroup

func imbored(s string) {
	fmt.Printf("------%s finished ----\n", s)
	wg.Done()
}

func print(s string) {

	defer imbored(s)
	for i := 1; i <= 5; i++ {
		if i == 4 {
			panic("sorry I panicked! I can't run 4 times :(")
		}
		fmt.Println(s)
		time.Sleep(time.Second)
	}

}

func main() {
	wg.Add(1)
	go print("hi")
	wg.Add(1)
	go print("hello")
	wg.Wait()

}

Ah so when i==4 it panicked and the whole thing just wasted. So we need to recover from it.

package main

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

var wg sync.WaitGroup

func imbored(s string) {
	if r := recover(); r != nil{
		fmt.Println("Don't worry you did fine! It happens.")
		fmt.Println("recovered from panic ", r)
	}

	fmt.Printf("------%s finished ----\n", s)
	wg.Done()
}

func print(s string) {

	defer imbored(s)
	for i := 1; i <= 5; i++ {
		if i == 4 {
			panic("sorry I panicked! I can't run 4 times :(")
		}
		fmt.Println(s)
		time.Sleep(time.Second)
	}

}

func main() {
	wg.Add(1)
	go print("hi")
	wg.Add(1)
	go print("hello")
	wg.Wait()

}
(You can say, “thats what she said!”)

Ah. So now its finally over.

I will write a separate article ‘channels’

Happy coding!

About the author

vigneshwar

Add comment

Leave a Reply

By vigneshwar

Most common tags

%d bloggers like this: