Wednesday, April 6, 2016

Go Channels for Absolute Beginners

Read Concurrency Parallelism and Goroutines for Beginners  before you proceed further.

CodeRef# 1

package main

import "fmt"

func main() {
 
      fmt.Println("A message from main function")
 
      go fmt.Println("A message from goroutine")
  
}


What do you think is the Output of above code?

A message from main function

Play with the Above Code

In most of the cases you would get the above output. But there can be cases where you may get both the messages as output. Here we can conclude that the output of the above code is undefined

Why didn't it always print the second message from the goroutine? 

The func main() here is running in a single OS execution thread. Once the main() code has finished writing "A message from main function"  it doesn't wait for the other subroutine (called using the keyword go) to finish its task.

A quick fix solution that can fix this issue is to introduce a delay in the main(). Something like:

 CodeRef# 2

package main

import "fmt"
import "time"

func main() {
 
      fmt.Println("A message from main function")
      go fmt.Println("A message from goroutine")
      time.Sleep(1 * time.Second) 
}


Output  CodeRef# 2


A message from main function
A message from goroutine

It seems we've achieved what we expected out of that code snippet. Right?

Though we've got the expected result it is a good example of bad programming. Time based synchronization must be avoided as they are inefficient and might lead to ugly bugs. You never know if the process will take 1 second (as introduced in the above example) or more or less than 1 second. Also, the time of execution may differ on different machine configuration. Avoid it!

CodeRef# 3

package main

import "fmt"
import "runtime"

func main() {
 
      fmt.Println("A message from main function")
      go fmt.Println("A message from goroutine")
      runtime.Gosched() 
}

Output  CodeRef# 3

A message from main function
A message from goroutine

Play with CodeRef# 3

This looks like a better solution. Here Gosched() has switched the execution context so that the other goroutine can finish its task. Gosched() yields the processor, allowing other goroutines to run.

What are Channels?

Goroutines run concurrently as independent units and hence there must be some mechanism to synchronize the access to shared memory. This is essential to prevent deadlock like scenarios. Go has Channels to sync goroutines. 

 The Little Go Book by Karl Seguin describes channels as:

You can think Channel as a communication pipe between goroutines which is used to pass data. In other words, A goroutine that has data can pass it to another goroutine via a channel. The result is that, at any point in time, only one goroutine has access to the data.

Remember, a channel can only transmit data-items of one datatype.

Type of Channels
  • Unbuffered or Synchronous
  • Buffered or Asynchronous
Declaring & Allocating Memory to Channels


                               intC := make(chan int)          //default capacity = 0
                               strC := make(chan string, 3) // non-zero capacity

Channel Supports 2 operations SEND and RECEIVE 

                               Channel <- Data                     // SEND 'data' to a channel
                               msg :- <- Channel                  //RECEIVE a message & Assign it to a variable msg

                               r := make(<-chan bool)          // can only read from
                               w := make(chan<- []os.FileInfo) // can only write to

Note: The left arrow operator <- is used for both sending & receiving data. The arrow head of the operator points in the direction of data flow. The channel SEND & RECEIVE operations are Atomic i.e. they always complete without any interruption. 

 CodeRef# 4

package main

import (
 "fmt"
 "time"
)

func main() {

 ch := make(chan int)

 go iSend(ch)
 go iReceive(ch)
 time.Sleep(time.Second * 1)
}

func iSend(ch chan int) {
 ch <- 1
 ch <- 3
 ch <- 5
 ch <- 7

}

func iReceive(ch chan int) {
 var info int
 // infinite for loop, executes till 'ch' is empty
 for {
  info = <-ch
  fmt.Printf("%d ", info)
 }
}

Output  CodeRef# 4


1 3 5 7 

Play with the Code

CodeRef# 5

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"
import "time"

func main() {
 ch1 := make(chan int)
 go send(ch1)
 go receive(ch1)
 time.Sleep(time.Second * 1)
}

func send(ch chan int) {
 for i := 1; i < 7; i++ {
  ch <- i
 }
}

func receive(ch chan int) {
 for {
  fmt.Println(<-ch)
 }
}


Output  CodeRef# 5

1
2
3
4
5
6

Play the code here 


CodeRef# 6

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import "fmt"
import "time"

func main() {
 ch := make(chan int)
 go sendOdd(ch)
 go sendEven(ch)
 go receive(ch)
 time.Sleep(time.Second * 1)
}

//This func to generate only Odd numbers
func sendOdd(ch chan int) {
 for i := 1; i < 9; i++ {
  if i%2 != 0 {
   ch <- i
  }
 }
}

//This func to generate only Even numbers
func sendEven(ch chan int) {
 for i := 1; i < 9; i++ {
  if i%2 == 0 {
   ch <- i
  }
 }
}

//Recive & Print numbers
func receive(ch chan int) {
 for {
  v := <-ch
  fmt.Println(v)
 }
}

Play with the Code

Output  CodeRef# 6

1
2
4
6
8
3
5
7


The above code is self explanatory. Play around and you'll soon get the knack of it. Please share your views to improve this article.

References

http://guzalexander.com/2013/12/06/golang-channels-tutorial.html