How to manage Go channels using range and close
Tags: go, tutorial, beginners, showdev
Published: 2020-04-20 14:35:02

This blog will go over how to use `range` to read data from a channel and `close` to shut it down. 

> To run the different cases/examples, please use the [code on GitHub](https://github.com/abhirockzz/just-enough-go/blob/master/channels-range-close/channels-range-close.go)

You can use `<-` (e.g. `<-myChannel`) to accept values from a channel. Here is an example:

### Simple scenario

```
func f1() {
	c := make(chan int)

	//producer
	go func() {
		for i := 1; i <= 5; i++ {
			c <- i
			time.Sleep(1 * time.Second)
		}
	}()

	//consumer
	go func() {
		for x := 1; x <= 5; x++ {
			i := <-c
			fmt.Println("i =", i)
		}
		fmt.Println("press ctrl+c to exit")
	}()

	e := make(chan os.Signal)
	signal.Notify(e, syscall.SIGINT, syscall.SIGTERM)
	<-e
}
```

The producer goroutine sends five integers and the consumer goroutine accepts them. The fact that the number of records exchanged (five in this example) is fixed/known makes means that this is an ideal scenario. The consumer knows exactly when to finish/exit

If you run this: the receiver will print 1 to 5, asking you to exit

> To run, simply uncomment `f1()` in `main` and run the program using `go run channels-range-close.go`

```
i = 1
i = 2
i = 3
i = 4
i = 5
consumer finished. press ctrl+c to exit
producer finished
^C
```

### goroutine leak

This was an oversimplified case. Let's make a small change by removing the `for` loop counter and converting it into an `infinite` loop - this is to simulate a scenario where the receiver wants to get all the values sent by the producer but does not know the specifics i.e. how many values will be sent (in real applications, this is often the case)

```
	//consumer
	go func() {
		for {
			i := <-c
			fmt.Println("i =", i)
		}
		fmt.Println("consumer finished. press ctrl+c to exit")
	}()
```

The output from the modified program is:

> To run, simply uncomment `f2()` in `main` and run the program using `go run channels-range-close.go`

```
i = 1
i = 2
i = 3
i = 4
i = 5
producer finished
```

Notice that the producer goroutine exited but you did not see the `consumer finished. press ctrl+c to exit` message. Once the producer is done sending five integers and the consumer receives all of them, it's just stuck there waiting for next value `i := <-c` and will not be able to return/exit

> In long running programs, this results in a `goroutine leak`

### `range` and `close` to the resuce!

This is where `range` and `close` can help:

- `range` provides a way to iterate over values of a channel (just like you would for a slice)
- `close` makes it possible to signal to consumers of a channel that there is nothing else which will be sent on this channel

Let's refactor the program. First, change the consumer to use `range` - remove the `i := <-c` bit and replace it with `for i := range c`


```
	go func() {
		for i := range c {
			fmt.Println("i =", i)
		}
		fmt.Println("consumer finished. press ctrl+c to exit")
	}()
```

Update the producer goroutine to add `close(c)` outside the `for` loop. This will ensure that the consumer goroutine gets the signal that there is nothing more to come from the channel and the `range` loop will terminate!

```
	go func() {
		for i := 1; i <= 5; i++ {
			c <- i
			time.Sleep(1 * time.Second)
		}
		close(c)
        fmt.Println("producer finished")
	}()
```

If you run the program now, you should see this output:

> To run, simply uncomment `f3()` in `main` and run the program using `go run channels-range-close.go`

```
i = 1
i = 2
i = 3
i = 4
i = 5
producer finished
consumer finished. press ctrl+c to exit
```

### bonus

The consumer goroutine does not have to co-exist with the producer goroutine to receive the values i.e. even if the producer goroutine finishes (and closes the channel), the consumer goroutine `range` loop will receive all the values - this is helpful when the consumer is processing the records sequentially.

We can simulate this scenario by using a combination of:

- a buffered channel in the producer, and
- delay the consumer goroutine by adding a `time.Sleep()`

In the producer, we create a buffered channel of capacity five `c := make(chan int, 5)`. This is to ensure that producer goroutine will not block in the absence of a consumer:

```
	c := make(chan int, 5)
	
	//producer
	go func() {
		for i := 1; i <= 5; i++ {
			c <- i
		}
		close(c)
		fmt.Println("producer finished")
	}()
```

The consumer remains the same, except for the `time.Sleep(5 * time.Second)` which allows the producer goroutine to exit before consumer can start off

```
	go func() {
		time.Sleep(5 * time.Second)
        fmt.Println("consumer started")

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

		fmt.Println("consumer finished. press ctrl+c to exit")
	}()
```

Here is the output you should see:

> To run, simply uncomment `f4()` in `main` and run the program using `go run channels-range-close.go`

```
producer finished
consumer started
i = 1
i = 2
i = 3
i = 4
i = 5
consumer finished. press ctrl+c to exit
```

The producer goroutine finished sending five records, the consumer woke up after a while, received and printed out all the five messages sent by the producer.

That's all for now. Stay tuned for upcoming posts in the series!