Intro to Concurrency in Golang
Tags: go, concurrency, backenddevelopment
Published: 2022-05-26 05:39:46

---
title: Intro to Concurrency in Golang
published: true
date: 2022-05-25 00:45:47 UTC
tags: go, concurrency, backendDevelopment 
canonical_url: https://vietmle.com/posts/go_conc_intro/
---

When dealing with conccurency problem, it is harder to reason about when moving down the stack of abstraction (machine, process, thread, hardware components, etc). Most programming languages use thread as its highest level of abstraction. Fortunately, Go build on top of that & introduce **Goroutine**.

> “Share memory by communicating, don’t communicate by sharing memory.” - One of Go’s mottos

Although Golang provides traditional locking mechanism in `sync` package, its philosophy prefers “share memory by communicating”. Therefore, Golang introduces **channel** as the medium for **Goroutines** to communicate with each other.

```
//goroutine
go func() { 
	// do some work
}()

//channel
dataStream := make(chan interface{})

```

## Fork-join model

![Fork-join concurrency model that Go follows](https://vietmle.com/images/Go_con_model.png)_Fork-join concurrency model that Go follows_

This concurrency model is used in Golang. At anytime; a child Goroutine can be _fork_ to do concurrent work with its parent & then will _join_ back \*\*at some point.

Every Go program has _a main Goroutine_. The main one can be exit earlier than its children; as a result, a join point is needed to make sure children Goroutine has chance to finish.

## Channel

Channel holds the following properties:

- Goroutine-safe (Multiple Goroutines can access to shared channel without race condition)
- FIFO queue semantics

Channel always return 2 value: 1 is object returned, 1 is status (`true` means valid object, `false` means no more values will be sent in this channel)

```
intStream := make(chan int)
close(intStream)
integer, ok := <- intStream 
fmt.Printf("(%v): %v", ok, integer) // (false): 0

```

### Channel direction

```
var dataStream <-chan interface{} //read from only channel
var dataStream chan<- interface{} // write to only channel
var dataStream chan interface{} // 2 ways

```

### Channel capacity

Default capacity of a channel is 0 (unbuffered channel). Reading from empty or writing to full channel is blocking.

```
c := make(chan int,10) //buffered size of 10
c := make(chan int) //unbuffered channel

```

> if a buffered channel is empty and has a receiver, the buffer will be bypassed and the value will _be passed directly from the sender to the receiver._

### Channel behavior against Goroutine

When a Goroutine read from or write to a channel, various of behaviours might happen depending on channel state.

```
intStream := make(chan int) // 0 capacity channel

go func() {
	defer close(intStream) 
	for i:=1; i<=5; i++{
	  intStream <- i
  }
}()

//range from a channel
for integer := range intStream { // always blocked until the channel is closed
	fmt.Printf("%v ", integer)
}

```

Below table summarizes all the behaviour:

| Operations | Channel state | Result |
| --- | --- | --- |
| Read | nil | Block |
| | Open & not empty | Value |
| | Open & empyt | Block |
| | Closed | [default value], `false` |
| | Write only | Compile error |
| Write | nil | Block |
| | Open & full | Block |
| | Open & not full | Write value |
| | Closed | `panic` |
| | Receive only | Compile error |
| close | nil | `panic` |
| | Open & not empty | Closes channel. Subsequent reads succeed value until channel is empty, then it reads deafult value. |
| | Open & empty | Closes channel. Reads produces default value. |
| | Closed | `panic` |
| | Receive only | Compile Error |

As the behaviour is clomplex, we should have a way to make a robust and scalable program.

## Robust & scalable way when working with channel

Here is 1 suggestion way:

- At most 1 Goroutine have the ownership of a channel. The channel ownership should be small to be managable.
- Channel owner have a write-access (`chan←`);
- While consumer only have read-only view (`←chan`).

1 **Channel owners**

  Responsibilities:

  - Init channel
  - Do write to channel or pass ownership to another goroutine
  - Close channel
  - Encapsulate & expose channel as a reader channel

2 **Channel**  **consumer**

  Responsibilities :

  - Handle when channel is closed
  - Handle blocking behaviour when reading from channel

```
chanOwner := func() <-chan int { 
	resultStream := make(chan int, 5) //init
	go func() {
		defer close(resultStream) // close
		for i:=0;i<=5;i++{ 
			resultStream <- i // write
	} }()
	return resultStream // read channel returned
}

resultStream := chanOwner()
for result := range resultStream {
  fmt.Printf("Received: %d\n", result)
}
fmt.Println("Done receiving!")

```

**Refereneces:**

Concurrency in Go: Tools and Techniques for Developers - Book by Katherine Cox-Buday