Penggunaan Channel Select Range Timeout
Adanya channel sangat membantu kita untuk mengelola goroutine yang sedang berjalan pada program kita. Ada kalanya kita juga perlu mengelola goroutine banyak dan dibutuhkan banyak channel juga. Maka, disinilah kegunaan select. select memudahkan kita mengontrol komunikasi data lewat channel. Cara penggunannya sama seperti seleksi kondisi switch.
Program pencarian rata-rata dan nilai tertinggi berikut merupakan contoh sederhana penerapan select dalam channel. Akan ada 2 buah goroutine yang masing-masing di-handle oleh sebuah channel. Setiap kali goroutine selesai dieksekusi, akan dikirimkan datanya ke channel yang bersangkutan. Lalu dengan menggunakan select, akan dikontrol penerimaan datanya. Pertama, kita siapkan terlebih dahulu 2 fungsi yang akan dieksekusi sebagai goroutine baru. Fungsi pertama digunakan untuk mencari rata-rata, dan fungsi kedua untuk penentuan nilai tertinggi dari sebuah slice.
Baiklah, kita akan mencoba mengimplementasikan program sederhana untuk pencarian rata-rata dan nilai tertinggi dari suatu bilangan array. Pada program ini akan kita definisikan 2 buah goroutine yang masing-masing memiliki fungsi yang akan di-handle oleh sebuah channel.
Setiap kali goroutine selesai dieksekusi maka akan dikirimkan datanya ke channel yang bersangkutan, lalu dengan menggunakan select akan dikontrol penerimaan datanya.
Kita siapkan terlebih dahulu 2 fungsi yang akan dieksekusi sebagai goroutine baru yaitu fungsi pertama mencari rata-rata dan fungsi kedua menentukan nilai tertinggi dari sebuah array slice.
1package main
2
3func getAverage(numbers []int, ch chan float64) {
4 var sum = 0
5 for _, e := range numbers {
6 sum += e
7 }
8 ch <- float64(sum) / float64(len(numbers))
9}
10
11func getMax(numbers []int, ch chan int) {
12 var max = numbers[0]
13 for _, e := range numbers {
14 if max < e {
15 max = e
16 }
17 }
18 ch <- max
19}main sebagai goroutine baru. Setelah itu kedua fungsi tersebut akan mengirimkan datanya ke dalam channel yang sudah ditentukan, yang mana pada program ini kita akan membedakan variabel penampungnya. ch1 menampung untuk nilai rata-rata, ch2 untuk hasil data nilai tertinggi.Setelahnya, mari kita buat fungsi main dibawah ini.
1func main() {
2 runtime.GOMAXPROCS(2)
3
4 var numbers = []int{3, 4, 3, 5, 6, 3, 2, 2, 6, 3, 4, 6, 3}
5 fmt.Println("numbers : ", numbers)
6
7 var ch1 = make(chan float64)
8 go getAverage(numbers, ch1)
9
10 var ch2 = make(chan int)
11 go getMax(numbers, ch2)
12
13 for i := 0; i < 2; i++ {
14 select {
15 case avg := <-ch1:
16 fmt.Printf("Avg \t: %.2f \n", avg)
17 case max := <-ch2:
18 fmt.Printf("Max \t: %d \n", max)
19 }
20 }
21}Pada kode di atas, transaksi pengiriman data pada channel ch1 dan ch2 dikontrol menggunakan select . Terdapat 2 buah case kondisi penerimaan data dari kedua channel tersebut. Kondisi case avg := <-ch1 akan terpenuhi ketika ada penerimaan data dari channel ch1 , yang kemudian akan ditampung oleh variabel avg . Kondisi case max := <-ch2 akan terpenuhi ketika ada penerimaan data dari channel ch2 , yang kemudian akan ditampung oleh variabel max . Karena ada 2 buah channel, maka perlu disiapkan perulangan 2 kali sebelum penggunaan keyword select .
Pada kode di atas, pengiriman data pada channel ch1 dan ch2 akan dikontrol oleh select. Terdapat 2 case kondisi dimana penerimaan data dari kedua tersebut akan dikirim melalui channel tersebut.
- Kondisi
case avg := <-ch1akan terpenuhi ketika penerimaan data dari channelch1yang kemudian akan ditampung pada variabelavg. - Kondisi
case max := <-ch2akan terpenuhi ketika penerimaan data dari channelch2yang kemudian akan ditampung pada variabelmax.
Dikarenakan ada 2 buah channel maka kita perlu disiapkan untuk melakukan perulangan sebanyak 2 kali sebelum penggunaan keyword select.
1➜ channel-select git:(main) ✗ go run main.go
2numbers : [3 4 3 5 6 3 2 2 6 3 4 6 3]
3Max : 6
4Avg : 3.85
5➜ channel-select git:(main) ✗ go run main.go
6numbers : [3 4 3 5 6 3 2 2 6 3 4 6 3]
7Avg : 3.85
8Max : 6 Channel - Range and Close
Penerimaan data lewat channel yang dipakai oleh banyak goroutine, akan lebih mudah dengan memanfaatkan keyword for - range. Hal ini bisa kita terapkan untuk melakukan perulangan tanpa henti.
Perulangan tersebut tetap berjalan meski tidak ada transaksi pada channel dan hanya akan berhenti jika status channel berubah menjadi closed atau sudah ditutup. Fungsi close digunakan utuk menutup channel.
Channel yang sudah ditutup tidak akan bisa digunakan kembali untuk menerima dan mengirim data sehingga menjadikan for - range pun akan ikut berhenti juga.
Misalkan kita akan membuat contoh implementasinya seperti dibawah ini.
Kita akan membuat suatu fungsi sendMessage dengan parameter channel yang mana jika fungsi itu dijalankan akan melakukan perulangan sebanyak 20 kali dan setelah semua terkirim maka akan diakhiri dengan channel close.
1func sendMessage(ch chan<- string) {
2 for i := 0; i < 20; i++ {
3 ch <- fmt.Sprintf("data %d", i)
4 }
5 close(ch)
6}printMessage() untuk handle penerimaan data. Didalamnya, channel akan di-looping menggunakan for - range yang kemudian ditampilkan datanya.1func printMessage(ch <-chan string) {
2 for message := range ch {
3 fmt.Println(message)
4 }
5}Setelah itu kita akan buat channel baru di fungsi main dengan menjalankan sendMessage() sebagai goroutine dan fungsi printMessage juga akan kita jalankan. Maka, dengan ini kita akan mengirimkan 20 data lewat goroutine baru dan juga akan diterima oleh goroutine juga.
1func main() {
2 runtime.GOMAXPROCS(2)
3 var messages = make(chan string)
4 go sendMessage(messages)
5 printMessage(messages)
6}Jika 20 data itu sukses dikirim dan diterima maka channel ch akan dimatikan close, sehingga perulangan data channel dalam printMessage() juga akan berhenti.
1➜ channel-range-close git:(main) ✗ go run main.go
2data 0
3data 1
4data 2
5data 3
6data 4
7data 5
8data 6
9data 7
10data 8
11data 9
12data 10
13data 11
14data 12
15data 13
16data 14
17data 15
18data 16
19data 17
20data 18
21data 19Channel Direction
Golang memiliki keunikan dalam fitur parameter channel yang sudah disediakan. Level akses channel bisa ditentukan, apakah kita hanya sebagai penerima, pengirim atau bahkan sekaligus penerima dan pengirim. Konsep ini disebut dengan channel direction.
Cara pemberian level akses ini dengan menambahkan tanda <- sebelum atau setelah keyword chan. Untuk lebih jelasnya bisa dilihat di tabel berikut.
| Sintaks | Penjelasan |
|---|---|
ch chan string | Parameter ch bisa digunakan untuk mengirim dan menerima data |
ch chan<- string | Parameter ch hanya bisa digunakan untuk mengirim data |
ch <-chan string | Parameter ch hanya bisa digunakan untuk menerima data |
Secara default channel akan memiliki kemampuan untuk mengirim dan menerima data. Agar channel tersebut hanya bisa mengirim atau menerima saja, maka kita perlu memanfaatkan simbol <-.
Sebagai contoh fungsi sendMessage(ch chan<- string) yang parameter ch dideklarasikan dengan level akses untuk pengiriman data saja. Channel tersebut hanya bisa digunakan untuk mengirim data, contohnya: ch <- fmt.Sprintf("data %d", i).
Dan sebaliknya pada fungsi printMessage(ch <-chan string), channel ch hanya bisa digunakan untuk menerima data saja.
Channel - Timeout
Mendefinisikan channel agar bisa mengontrol penerimaan data dari channel berdasarkan waktu diterimanya maka kita perlu pengecekan durasi timeout dengan kita bisa menentukan sendiri. Misalkan ketika tidak ada aktivitas penerimaan data selama durasi tertentu, maka akan memicu callback yang isinya juga ditentukan sendiri.
Berikut adalah program sederhana tentang pengaplikasian timeout pada channel. Sebuah goroutine baru dijalankan dengan tugas mengirimkan data setiap interval tertentu, dengan durasi interval-nya adalah acak/random.
1package main
2
3import (
4 "math/rand"
5 "time"
6)
7
8func sendData(ch chan<- int) {
9 for i := 0; true; i++ {
10 ch <- i
11 time.Sleep(time.Duration(rand.Int()%10+1) * time.Second) // fungsi tertentu yg proses-nya lama, kadang2
12 }
13}select. 1func retreiveData(ch <-chan int) {
2loop:
3 for {
4 select {
5 case data := <-ch:
6 fmt.Print(`receive data "`, data, `"`, "\n")
7 case <-time.After(time.Second * 5):
8 fmt.Println("timeout. no activities under 5 seconds")
9 break loop
10 }
11 }
12}retrieveData terdapat 2 kondisi yang mana ini terjadi jikacase data := <- messages:maka akan terpenuhi ketika ada serah terima data pada channelmessagescase <- time.After(time.Second * 5):maka akan terpenuni ketika tidak ada aktivitas penerimaan data dari channel dalam durasi waktu 5 detik.
Maka diakhir kita akan eksekusi fungsi tersebut pada fungsi main. Beriku ini lebih lengkapnya.
1func main() {
2 rand.Seed(time.Now().Unix())
3 runtime.GOMAXPROCS(2)
4
5 var messages = make(chan int)
6
7 go sendData(messages)
8 retreiveData(messages)
9} 1➜ channel-timeout git:(main) ✗ go run main.go
2receive data "0"
3receive data "1"
4timeout. no activities under 5 seconds
5➜ channel-timeout git:(main) ✗ go run main.go
6receive data "0"
7timeout. no activities under 5 seconds
8➜ channel-timeout git:(main) ✗ go run main.go
9receive data "0"
10timeout. no activities under 5 seconds
11➜ channel-timeout git:(main) ✗ go run main.go
12receive data "0"
13receive data "1"
14receive data "2"
15timeout. no activities under 5 seconds