BAB 36: Timer dan Ticker

Pelajari cara menjadwalkan eksekusi kode di Go menggunakan time.Timer untuk penundaan sekali jalan dan time.Ticker untuk eksekusi berulang berkala.

Di bab sebelumnya kita bekerja dengan time.Time sebagai nilai — menyimpan, memformat, dan membandingkan tanggal. Tapi package time juga menyediakan mekanisme lain: mengeksekusi kode berdasarkan berjalannya waktu. Kamu sudah melihat ini sekilas di Bab 27 saat menggunakan time.After untuk channel timeout. Di bab ini kita akan memahami lebih dalam dua primitif yang menjadi fondasi penjadwalan di Go: time.Timer untuk eksekusi sekali jalan setelah jeda tertentu, dan time.Ticker untuk eksekusi yang berulang secara periodik.

time.Sleep: Penundaan Sederhana

Sebelum masuk ke Timer dan Ticker, ada fungsi yang paling sederhana: time.Sleep. Ia memblokir goroutine yang sedang berjalan selama durasi yang ditentukan.

// jadwal.go
package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("mulai proses...")
    time.Sleep(2 * time.Second)
    fmt.Println("selesai setelah 2 detik")
}
mulai proses...
selesai setelah 2 detik

time.Sleep berguna untuk jeda sederhana, tapi ia memblokir sepenuhnya — tidak ada cara untuk membatalkannya di tengah jalan. Untuk kontrol yang lebih presisi, Timer dan Ticker adalah pilihan yang lebih tepat.

time.Timer: Countdown Sekali Jalan

time.Timer adalah objek yang mengirimkan satu nilai ke channel-nya setelah durasi yang ditentukan. Ini adalah countdown yang terpicu satu kali.

// jadwal.go
package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("menunggu konfirmasi...")

    penghitung := time.NewTimer(3 * time.Second)
    <-penghitung.C

    fmt.Println("waktu habis — lanjut ke langkah berikutnya")
}
menunggu konfirmasi...
waktu habis — lanjut ke langkah berikutnya

time.NewTimer(d) membuat timer dengan durasi d. Property .C adalah channel yang akan menerima satu nilai time.Time setelah durasi berakhir. Ekspresi <-penghitung.C memblokir sampai nilai itu tiba.

Perbedaan utama dari time.Sleep: timer bisa dibatalkan sebelum terpicu menggunakan method .Stop().

// jadwal.go
package main

import (
    "fmt"
    "time"
)

func main() {
    batas := time.NewTimer(5 * time.Second)

    // simulasi: kondisi terpenuhi lebih awal
    go func() {
        time.Sleep(1 * time.Second)
        batas.Stop()
        fmt.Println("timer dibatalkan — kondisi terpenuhi lebih awal")
    }()

    select {
    case <-batas.C:
        fmt.Println("waktu habis")
    case <-time.After(6 * time.Second):
        fmt.Println("timeout keseluruhan")
    }
}
timer dibatalkan — kondisi terpenuhi lebih awal

.Stop() mengembalikan true jika timer berhasil dihentikan sebelum terpicu, dan false jika timer sudah terpicu. Setelah Stop(), channel .C tidak akan pernah menerima nilai — jadi goroutine yang menunggu <-timer.C akan terblokir selamanya kecuali kamu tangani dengan select.

time.AfterFunc: Callback Berbasis Waktu

time.AfterFunc menjalankan fungsi di goroutine baru setelah durasi tertentu — tanpa perlu menunggu channel secara eksplisit.

// jadwal.go
package main

import (
    "fmt"
    "time"
)

func main() {
    selesai := make(chan struct{})

    time.AfterFunc(2*time.Second, func() {
        fmt.Println("pengingat: laporan harus dikirim!")
        close(selesai)
    })

    fmt.Println("program berjalan, menunggu pengingat...")
    <-selesai
}
program berjalan, menunggu pengingat...
pengingat: laporan harus dikirim!

time.AfterFunc mengembalikan *time.Timer yang juga bisa dihentikan dengan .Stop() sebelum fungsi dieksekusi. Pola ini berguna untuk timeout dengan cleanup action, atau untuk penjadwalan tugas ringan tanpa goroutine manual.

time.Ticker: Eksekusi Periodik

Jika Timer terpicu satu kali, time.Ticker terpicu berulang kali setiap interval yang ditentukan. Setiap interval, ia mengirimkan nilai time.Time ke channel .C-nya.

// jadwal.go
package main

import (
    "fmt"
    "time"
)

func main() {
    denyut := time.NewTicker(1 * time.Second)
    defer denyut.Stop()

    hitungan := 0
    for t := range denyut.C {
        hitungan++
        fmt.Printf("[%s] detak ke-%d\n", t.Format("15:04:05"), hitungan)

        if hitungan >= 5 {
            break
        }
    }

    fmt.Println("ticker selesai")
}
[09:15:01] detak ke-1
[09:15:02] detak ke-2
[09:15:03] detak ke-3
[09:15:04] detak ke-4
[09:15:05] detak ke-5
ticker selesai

time.NewTicker(d) membuat ticker dengan interval d. Selalu panggil .Stop() ketika ticker tidak lagi dibutuhkan — tanpa itu, goroutine internal ticker terus berjalan dan channel .C terus diisi, menyebabkan goroutine leak.

Berbeda dari Timer, Ticker tidak berhenti sendiri. Jika kamu tidak memanggil .Stop(), ticker akan terus berjalan selama program hidup meskipun tidak ada yang membaca channel-nya. Gunakan defer ticker.Stop() segera setelah membuat ticker.

Kombinasi Timer dan Goroutine

Kekuatan nyata Timer terlihat ketika dikombinasikan dengan goroutine dan select — memungkinkan program melakukan sesuatu sambil menunggu, dan merespons lebih awal jika kondisi terpenuhi.

// jadwal.go
package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "time"
)

func main() {
    batasTunggu := time.NewTimer(5 * time.Second)
    masukan := make(chan string)

    go func() {
        fmt.Print("masukkan nama kamu (5 detik): ")
        scanner := bufio.NewScanner(os.Stdin)
        if scanner.Scan() {
            masukan <- strings.TrimSpace(scanner.Text())
        }
    }()

    select {
    case nama := <-masukan:
        batasTunggu.Stop()
        fmt.Printf("halo, %s!\n", nama)
    case <-batasTunggu.C:
        fmt.Println("\nwaktu habis — tidak ada input")
    }
}

Program ini membaca input dari pengguna sambil menjalankan countdown 5 detik secara bersamaan. Siapa pun yang menang — input tiba lebih dahulu, atau timer habis — itulah yang diproses. Pola ini adalah implementasi nyata dari “input dengan timeout” yang berguna di CLI tool interaktif.

Ticker untuk Health Check Periodik

Ticker sangat cocok untuk tugas yang harus dijalankan secara berkala: health check, pembersihan cache, penulisan metrik, atau sinkronisasi data.

// jadwal.go
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func cekStatus() string {
    if rand.Intn(4) == 0 {
        return "GAGAL"
    }
    return "OK"
}

func main() {
    pantau := time.NewTicker(2 * time.Second)
    berhenti := time.NewTimer(10 * time.Second)
    defer pantau.Stop()

    fmt.Println("memulai monitoring sistem...")

    for {
        select {
        case t := <-pantau.C:
            status := cekStatus()
            fmt.Printf("[%s] status: %s\n", t.Format("15:04:05"), status)
        case <-berhenti.C:
            fmt.Println("monitoring selesai")
            return
        }
    }
}
memulai monitoring sistem...
[09:20:02] status: OK
[09:20:04] status: OK
[09:20:06] status: GAGAL
[09:20:08] status: OK
[09:20:10] status: OK
monitoring selesai

Pola for { select { ... } } dengan dua channel — satu ticker periodik dan satu timer batas waktu — adalah idiom standar untuk proses monitoring dengan durasi terbatas di Go.

Latihan

Latihan 1 — Countdown visual: Buat program yang mencetak countdown dari 10 ke 0, satu angka per detik menggunakan time.Ticker. Setelah mencapai 0, cetak “Selesai!” dan hentikan ticker.

Latihan 2 — Timer dengan fallback: Buat fungsi tunggDenganBatas(tugas func() string, batas time.Duration) (string, bool) yang menjalankan tugas di goroutine terpisah. Jika tugas selesai sebelum batas, kembalikan hasilnya dengan true. Jika waktu habis lebih dulu, kembalikan string kosong dengan false.

Latihan 3 — Scheduler sederhana: Buat program yang menjalankan tiga “pekerjaan” secara periodik dengan interval berbeda: setiap 1 detik cetak waktu saat ini, setiap 3 detik cetak “backup dilakukan”, dan setiap 5 detik cetak “laporan dikirim”. Jalankan selama 15 detik menggunakan goroutine dan ticker terpisah untuk masing-masing pekerjaan.

Timer dan Ticker melengkapi pemahaman kamu tentang package time secara menyeluruh — dari merepresentasikan waktu sebagai nilai, hingga mengorkestrasi eksekusi kode berdasarkan berjalannya waktu. Go yang kamu pahami sekarang adalah fondasi yang solid — cukup untuk menulis CLI tool yang berguna, API server yang efisien, sistem concurrency yang menangani ribuan request, hingga scheduler yang mengelola tugas-tugas periodik secara tepat waktu. Langkah selanjutnya sepenuhnya ada di tanganmu.

Referensi

  1. 1time.Timer — Go Standard Library Documentation
  2. 2time.Ticker — Go Standard Library Documentation
  3. 3Tickers — Go by Example