Sederhananya goroutine dapat didefinisikan sebagai fungsi yang dijalankan langsung oleh program secara independen di latar belakang, sementara seluruh program tersebut tetap berjalan. Tentu ini berkaitan dengan dengan concurrency dimana fungsi tersebut kita anggap komponen program dan komponen tersebut berjalan secara independen. Tapi disini kita tidak akan membahas concurrency secara mendetail.

Membuat goroutine

Ada dua cara untuk membuat goroutine, pertama menggunakan fungsi seperti biasanya dan fungsi anonymous. Keduanya cara tersebut pada dasarnya sama hanya saya penempatan penulisan fungsinya yang berbeda.

package main

import (
	"fmt"
)

// fungsi utama program
func main() {
    // goroutine
	go fungsi()
}

// fungsi biasa
func fungsi() {
	for i := 0; i < 20; i++ {
		fmt.Print(i)
	}
}

Jika kita jalankan program di atas, tidak ada output yang ditampilkan dikarenakan program utamanya keluar dari proses setelah menjalankan satu statement goroutine go fungsi(). fungsi() tetap berjalan, tapi sudah tidak ada antarmuka untuk menampilkan datanya. Untuk dapat menampilkan data pada fungsi fungsi() program perlu berisi tugas lain misalnya kita rubah programnya sedikit seperti berikut:

//fungsi utama
func main() {
    //goroutine
	go fungsi()
    
    //kesibukan program  
    for i := 1; i < 300; i++ {
        //menggambar garis
		fmt.Print("-")
	}
}

Sekarang setiap kita menjalankan program ada kalanya output dari fungsi goroutine ditampilkan. Jika kamu hanya melihat output garisnya saja, kamu hanya perlu menjalankan programnya berulang ulang. jika kamu beruntung kamu bisa melihat output seperti ini atau output lebih lengkap lagi.

------------------------------------012345-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Sekarang kita coba ubah kesibukan program utamanya sebagai goroutine, tapi menggunakan anonymous function.

//fungsi utama
func main() {
    //goroutine
	go fungsi()
    
    //goroutine menggunakan anonymous function
    go func() {
        for i := 1; i < 300; i++ {
            //menggambar garis
            fmt.Print("-")
        }
    }()
}

Hasilnya akan tidak kelihatan lagi, karena semuanya berjalan sendiri - sendiri. Untuk bisa melihat sebagian output atau seluruhnya (tergantung kecepatan proses cpu), kita bisa tunggu sebentar dengan mendelay fungsi utama untuk menutup program dengan fungsi time.Sleep() seperti berikut:

package main

import (
	"fmt"
    "time"
)

//fungsi utama
func main() {
    //goroutine
	go fungsi()
    
    //goroutine menggunakan anonymous function
    go func() {
        for i := 1; i < 300; i++ {
            //menggambar garis
            fmt.Print("-")
        }
    }()
    //delay close fungsi utama 1 detik
    time.Sleep(1 * time.Second)
}

Membuat Goroutine Sebanyak-banyaknya

Sebelumnya kita sudah melihat bagaimana setiap komponen program berjalan secara concurren, dimana setiap fungsi yang kita buat berjalan secara independen. Sekarang kita buat program dengan sebanyak goroutine yang kita inginkan untuk memahami sedikit tentang concurrency dan parallelisme.

package main

import (
	"flag"
	"fmt"
	"time"
)

func main() {
	n := flag.Int("n", 20, "Jumlah goroutine")
	flag.Parse()

	jumlah := *n
	fmt.Printf("Buat %d goroutine\n", jumlah)

	for i := 0; i < jumlah; i++ {
		go func(x int) {
			fmt.Printf("%d ", x)
		}(i)
	}
	time.Sleep(time.Second)
	fmt.Println("\nKeluar...")
}

Ketika kita jalankan program tersebut, hasil ouputnya akan sangat berbeda setiap kali program dijalankan tergantung jumlah prosesor pada komputer.

Golang to create 20 goroutines
1 19 14 8 9 10 6 11 0 12 2 13 3 4 16 7 17 5 18 15
Exiting...

Dari output diatas kita bisa melihat angka 0 sampai 19 adalah hasil output dari goroutine 1 sampai 20, inilah yang dimaksudkan dengan concurrency, yaitu dengan memecah-mecah komponen program untuk dijalankan secara independen dan menggabungkan outputnya entah berurutan atau tidak, yang penting masalah utamanya yaitu menampilkan datanya dapat tercapai.

Hal yang membuat outputnya tidak berurutan dikarenakan setiap komponen diproses di unit processor berbeda pada setiap cpu. Inilah yang disebut parallelism, dimana setiap unit processor (unit 1 s/d 4) memproses jenis komponen/entitas yang sama secara bersamaan.

Jadi desain concurrency adalah cara menstruktur program menjadi beberapa komponen sehingga bisa dijalankan secara parallel. semakin banyak komponen dapat diproses secara independen (jika memungkinkan), maka akan semakin cepat masalah yang dapat diselesaikan secara parallel.

Daripada kita menebak berapa lama fungsi goroutine selesai menjalankan tugasnya menggunakan fungsi time.Sleep(),cara lain untuk menunggu fungsi goroutine selesai adalah menggunakan sync.WaitGroup.

package main

import (
	"flag"
	"fmt"
	"sync"
)

func main() {
	n := flag.Int("n", 20, "Jumlah goroutine")
	flag.Parse()
	jumlah := *n
	fmt.Printf("Buat %d Goroutine.\n", jumlah)
	var waitGroup sync.WaitGroup

	for i := 0; i < jumlah; i++ {
		waitGroup.Add(1)
		go func(x int) {
			defer waitGroup.Done()
			fmt.Printf("%d ", x)
		}(i)
	}
	waitGroup.Wait()
	fmt.Println("\nKeluar...")
}

Disini kita membuat variabel waitGroup, mengubah nilai waitGroup dengan menggunakan fungsi waitGroup.Add(1) dengan parameter sesuai jumlah goroutine yang akan kita buat sebagai counter. Ketika goroutine selesai menjalankan tugasnya, pastikan untuk mengurangi nilai counter waitgroup dengan menjalankan fungsi waitGroup.Done().

Fungsi waitGroup.Wait() akan menunggu counter pada variabel waitGroup sama dengan kosong, dimana semua goroutine selesai menjalankan tugasnya.