Menggunakan chan chan pada golang

Tulisan ini merupakan catatan saya saat menggunakan chan chan (channel over channel) pada golang.

Pada bahasa pemrograman golang, channel merupakan mekanisme komunikasi antar goroutine yang dapat dianalogikan sebagai pipa dimana data dimasukkan pada ujung pertama, dan diambil pada ujung lainnya.

Jika channel merupakan jalur komunikasi __satu arah__, bagaimana jika seandainya ada 2 goroutine yang berbeda dan ingin berkomunikasi secara dua-arah? chan chan dapat mengakomodir hal ini. Karena apa yang dikirimkan ke channel tersebut merupakan channel lain yang digunakan untuk mengirimkan data kembali ke pengirim awal.

Perlu diingat, memindahkan data secara bidirectional menggunakan satu channel adalah hal yang perlu dihindari.

Pada catatan kali ini saya menggunakan buffered channel, karena salah satu goroutine akan menunda goroutine lainnya.

package main

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

type batch struct {
	// let's say we have this much data
	count int

	// WaitGroup was used to create delay so that "main" does not immediately exit when getting a "done" signal.
	// this will buy the consumer some time to complete its part.
	wg *sync.WaitGroup

	// the things
	output chan chan string

	// will be used to give a signal that all data has been sent to the channel.
	done chan bool
}

func newBatch(count int) batch {
	return batch{
		wg:     &sync.WaitGroup{},
		output: make(chan chan string, 1),
		done:   make(chan bool),
		count:  count,
	}
}

func consumer(c batch) {
	input := make(chan string)
	for {
        // this one will block the producer goroutine since producer is waiting for "a transport" to use.
		c.output <- input 
		select {
		case resp := <-input:
			// do something
			fmt.Printf("in: %s\n\n", resp)

			c.wg.Done() // this one will tell the main when a safe time to exit is.

			// for demo's observability sake
			time.Sleep(500 * time.Millisecond)
		}
	}
}

func producer(c batch) {
	var order int
	for {
		if order > c.count {
			fmt.Println("that's it, signaling done from producer")
			c.done <- true
			return
		}

		// tell the waitgroup, we have add new things to wait for
		c.wg.Add(1)

		// Pretending to send some data ..
		fmt.Printf("out: sending #%d\n", order)
		outChan := <-c.output
		outChan <- fmt.Sprintf("from producer. #%d", order)

		order++
		// for demo's observability sake
		time.Sleep(100 * time.Millisecond)
	}
}

func main() {
	limt := 10
	c := newBatch(limt)
	// this one make main will not exit before all the "task" succesfully executed.
	defer c.wg.Wait()

	// run the goroutine
	go consumer(c)
	go producer(c)

	// wait for exit signal, let's say you need to do some clean-up before closing the program.
	for {
		select {
		case done := <-c.done:
			if done {
				fmt.Println("Got done signal, Calling clean up")
				cleanup()
				return
			}
		}
	}
}

func cleanup() {
	fmt.Println("Pretending to do a clean up....")
}

Metode ini dapat digunakan jika ingin melakukan verifikasi jika sebuah goroutine sudah selesai melakukan aksinya. Untuk penjelasan lain tentang channel over channel di golang, silakan tonton video berikut ini:

Tinggalkan Balasan

This site uses Akismet to reduce spam. Learn how your comment data is processed.