summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <m.shulhan@gmail.com>2020-12-30 02:43:01 +0700
committerShulhan <m.shulhan@gmail.com>2020-12-30 02:43:01 +0700
commitd68bcb862ca0e2ceb958b1e0ed9b37f2755f35a2 (patch)
treedbad93e52c4d75918ccb7efcaf24f89db011f12a
parent107c23491b4a51df35ce40115cae64fca259c161 (diff)
downloadgolang-id-web-d68bcb862ca0e2ceb958b1e0ed9b37f2755f35a2.tar.xz
blog: terjemahkan "Introducing the Go Race Detector"
-rw-r--r--_content/blog/index.adoc6
-rw-r--r--_content/blog/race-detector/index.adoc346
-rw-r--r--_content/blog/race-detector/race-fix.go25
-rw-r--r--_content/blog/race-detector/race.go21
4 files changed, 398 insertions, 0 deletions
diff --git a/_content/blog/index.adoc b/_content/blog/index.adoc
index 97778c6..25ef76a 100644
--- a/_content/blog/index.adoc
+++ b/_content/blog/index.adoc
@@ -51,6 +51,9 @@
* link:/blog/slices[Array, slice (dan string): Mekanisme 'append'],
26 September 2013. _Rob Pike_
+* link:/blog/race-detector[Memperkenalkan pendeteksi _data race_],
+ 26 Juni 2013. _Dmitry Vyukov_ dan _Andrew Gerrand_.
+
* link:/blog/learn-go-from-your-browser[Belajar Go lewat peramban Anda],
4 Oktober 2011. _Andrew Gerrand_
@@ -142,3 +145,6 @@
* link:/doc/articles/race_detector.html[Pendeteksi _data race_] - Manual tentang
pendeteksi _data race_.
+
+* link:/blog/race-detector[Memperkenalkan pendeteksi _data race_] -
+ Pengenalan terhadap pendeteksi _data race_.
diff --git a/_content/blog/race-detector/index.adoc b/_content/blog/race-detector/index.adoc
new file mode 100644
index 0000000..8b17dd4
--- /dev/null
+++ b/_content/blog/race-detector/index.adoc
@@ -0,0 +1,346 @@
+= Memperkenalkan pendeteksi _data race_
+Dmitry Vyukov; Andrew Gerrand
+26 Juni 2013
+
+== Pendahuluan
+
+http://en.wikipedia.org/wiki/Race_condition[Kondisi berpacu (_race conditions_)]
+adalah salah satu dari kesalahan pemrograman yang berbahaya dan
+sukar ditangkap.
+Kesalahan ini biasanya menyebabkan kegagalan yang tidak menentu dan misterius,
+terkadang kegagalan ini muncul lama setelah kode berjalan.
+Walaupun mekanisme konkurensi Go membuat kita mudah menulis kode yang
+konkuren, tetapi ia tidak mencegah adanya kondisi berpacu.
+Perhatian, ketekunan, dan pengujian diperlukan.
+Dan perkakas yang tepat juga dapat membantu.
+
+Kami dengan gembira memperkenalkan
+link:/doc/articles/race_detector.html[pendeteksi _data race_]
+pada Go 1.1, sebuah perkakas baru untuk menemukan kondisi berpacu dalam kode
+Go.
+Sekarang ini tersedia pada sistem Linux, OS X, dan Windows dengan prosesor x86
+64-bit.
+
+Pendeteksi _data race_ didasari oleh
+https://github.com/google/sanitizers[pustaka _ThreadSanitizer_]
+dari C/C++,
+yang telah lama digunakan untuk mendeteksi banyak eror dalam basis kode
+internal Google dan
+http://www.chromium.org/[Chromium].
+Teknologi ini diintegrasikan pada Go di bulan September 2012; sejak itu ia
+telah menangkap
+https://github.com/golang/go/issues?utf8=%E2%9C%93&q=ThreadSanitizer[42 kondisi]
+berpacu dalam pustaka standar.
+Sekarang ia telah menjadi bagian dari proses pembangunan berkelanjutan, yang
+mana terus menangkap kondisi berpacu bila ia muncul.
+
+== Cara bekerja
+
+Pendeteksi _data race_ diintegrasikan dengan perkakas go.
+Saat opsi baris perintah `-race` di set, _compiler_ membaca semua akses memori
+dalam kode dan mencatat kapan dan bagaimana memori tersebut diakses, sementara
+pustaka _runtime_ membaca adanya akses yang tidak disinkronisasi ke variabel
+yang berbagi.
+Saat perilaku "berpacu" terdeteksi, sebuah peringatan dicetak.
+(Bacalah
+https://github.com/google/sanitizers/wiki/ThreadSanitizerAlgorithm[artikel berikut]
+untuk memahami lebih rinci tentang bagaimana algoritma bekerja).
+
+Pendeteksi _data race_ dapat mendeteksi kondisi berpacu hanya saat dipicu oleh
+kode yang sedang berjalan, yang artinya sangatlah penting untuk menjalankan
+program dengan opsi `-race` telah dinyalakan sebelum digunakan di lingkungan
+kerja yang sebenarnya.
+Namun, program yang dibangun dengan `-race` dapat menggunakan CPU dan memori
+sepuluh kali lebih banyak, jadi tidak praktis untuk selalu menjalankan
+pendeteksi _data race_.
+Salah satu cara untuk mengatasi dilema ini yaitu dengan menjalankan beberapa
+tes dengan pendeteksi _data race_ dinyalakan.
+Integrasi tes dan unit tes adalah kandidat yang bagus, secara mereka condong
+menggunakan bagian kode secara konkuren.
+Pendekatan lain yaitu dengan menjalankan program dengan pendeteksi _data race_
+bersamaan dengan beberapa program yang sama pada beberapa server yang berbeda.
+
+== Menggunakan pendeteksi _data race_
+
+Pendeteksi _data race_ terintegrasi dengan perkakas Go.
+Untuk membangun kode Anda dengan menyalakan pendeteksi _data race_, cukup
+tambahkan opsi `-race` pada baris perintah:
+
+----
+$ go test -race mypkg // pengujian paket
+$ go run -race mysrc.go // kompilasi dan menjalankan program
+$ go build -race mycmd // pembangunan program
+$ go install -race mypkg // pemasangan paket
+----
+
+Untuk mencoba sendiri pendeteksi _data race_, ambil dan jalankan contoh
+program berikut:
+
+----
+$ go get -race golang.org/x/blog/support/racy
+$ racy
+----
+
+== Contoh-contoh
+
+Berikut dua contoh masalah dunia nyata yang ditangkap oleh pendeteksi _data
+race_.
+
+=== Contoh 1: Timer.Reset
+
+Contoh pertama yaitu versi sederhana dari kesalahan nyata yang ditemukan
+oleh pendeteksi _data race_.
+Program ini menggunakan sebuah `time.Timer` untuk mencetak sebuah pesan
+setelah durasi waktu acak antara 0 sampai 1 detik.
+Hal ini terjadi berulang kali selama 5 detik.
+Program ini menggunakan `time.AfterFunc` untuk membuat sebuah `Timer` untuk
+pesan yang pertama dan kemudian menggunakan method `Reset` untuk menjadwalkan
+pesan selanjutnya, supaya dapat menggunakan ulang variabel `Timer` yang sudah
+ada.
+
+----
+11 func main() {
+12 start := time.Now()
+13 var t *time.Timer
+14 t = time.AfterFunc(randomDuration(), func() {
+15 fmt.Println(time.Now().Sub(start))
+16 t.Reset(randomDuration())
+17 })
+18 time.Sleep(5 * time.Second)
+19 }
+20
+21 func randomDuration() time.Duration {
+22 return time.Duration(rand.Int63n(1e9))
+23 }
+----
+
+Kode tersebut tampak masuk akal, namun pada kondisi tertentu ia akan gagal:
+
+----
+panic: runtime error: invalid memory address or nil pointer dereference
+[signal 0xb code=0x1 addr=0x8 pc=0x41e38a]
+
+goroutine 4 [running]:
+time.stopTimer(0x8, 0x12fe6b35d9472d96)
+ src/pkg/runtime/ztime_linux_amd64.c:35 +0x25
+time.(*Timer).Reset(0x0, 0x4e5904f, 0x1)
+ src/pkg/time/sleep.go:81 +0x42
+main.funcĀ·001()
+ race.go:14 +0xe3
+created by time.goFunc
+ src/pkg/time/sleep.go:122 +0x48
+----
+
+Apa yang terjadi?
+Menjalankan program dengan menyalakan pendeteksi _data race_ akan tampak lebih
+jelas:
+
+----
+==================
+WARNING: DATA RACE
+Read by goroutine 5:
+ main.funcĀ·001()
+ race.go:16 +0x169
+
+Previous write by goroutine 1:
+ main.main()
+ race.go:14 +0x174
+
+Goroutine 5 (running) created at:
+ time.goFunc()
+ src/pkg/time/sleep.go:122 +0x56
+ timerproc()
+ src/pkg/runtime/ztime_linux_amd64.c:181 +0x189
+==================
+----
+
+Pendeteksi _data race_ memperlihatkan masalahnya: pembacaan dan penulisan
+tanpa sinkronisasi pada variabel `t` dari goroutine yang berbeda.
+Jika durasi _timer_ awal sangat kecil, fungsi _timer_ bisa dipanggil sebelum
+`main` goroutine telah menyimpan nilai ke `t` sehingga pemanggilan `t.Reset`
+dilakukan pada `t` yang `nil`.
+
+Untuk memperbaiki kondisi berpacu ini kita mengubah kode untuk membaca dan
+menulis variabel `t` hanya dari `main` goroutine:
+
+----
+11 func main() {
+12 start := time.Now()
+13 reset := make(chan bool)
+14 var t *time.Timer
+15 t = time.AfterFunc(randomDuration(), func() {
+16 fmt.Println(time.Now().Sub(start))
+17 reset <- true
+18 })
+19 for time.Since(start) < 5*time.Second {
+20 <-reset
+21 t.Reset(randomDuration())
+22 }
+23 }
+----
+
+Di sini, `main` goroutine sajalah yang bertanggung jawab men-set dan me-reset
+Timer `t` dan kanal `reset` yang baru mengkomunikasikan kebutuhan untuk
+mereset _timer_ dengan cara yang aman.
+
+Pendekatan lain yang lebih sederhana dan kurang efisien yaitu dengan
+http://play.golang.org/p/kuWTrY0pS4[menghindari menggunakan timer yang sama].
+
+
+=== Contoh 2: `ioutil.Discard`
+
+Contoh kedua lebih halus.
+
+Paket `ioutil` memiliki objek
+https://golang.org/pkg/io/ioutil/#Discard[Discard]
+yang mengimplementasikan
+https://golang.org/pkg/io/#Writer[`io.Writer`],
+yang meniadakan semua data yang ditulis ke dalam objek tersebut.
+Seperti `/dev/null`: sebuah tempat mengirim data yang Anda bisa baca tapi
+tidak ingin disimpan.
+Objek `Discard` ini biasanya digunakan oleh
+https://golang.org/pkg/io/#Copy[`io.Copy`]
+untuk mengosongkan pembaca, seperti ini:
+
+----
+io.Copy(ioutil.Discard, reader)
+----
+
+Pada bulan Juli 2011, time Go menyadari bahwa menggunakan `Discard` dengan
+cara ini tidak efisien: fungsi `Copy` mengalokasikan penyangga sebesar 32 kB
+setiap kali dipanggil, namun saat digunakan dengan `Discard` penyangga
+tersebut tidak dipakai secara kita hanya akan melempar data yang dibaca saja.
+Kita memikirkan bahwa penggunaan idiomatis dari `Copy` dan `Discard` ini
+seharusnya tidak terlalu membebankan.
+
+Perbaikannya cukup sederhana.
+Jika `Writer` mengimplementasi method `ReadFrom`, sebuah pemanggilan `Copy`
+seperti berikut:
+
+----
+io.Copy(writer, reader)
+----
+
+didelegasikan ke pemanggilan yang lebih efisien:
+
+----
+writer.ReadFrom(reader)
+----
+
+Kita
+https://golang.org/cl/4817041[menambahkan method `ReadFrom`]
+ke tipe `Discard`, yang memiliki penyangga internal yang dibagi dengan semua
+penggunanya.
+Kita tahu bahwa secara teori ini adalah kondisi berpacu, namun secara semua
+penulisan ke penyangga seharusnya langsung dibuang kami berpikir masalah
+kondisi berpacu di sini tidak begitu penting.
+
+Saat pendeteksi _data race_ diimplementasikan ia langsung
+https://golang.org/issue/3970[menandakan kode tersebut]
+sebagai "berpacu".
+Sekali lagi, kita menyadari bahwa kode tersebut bermasalah, namun memutuskan
+bahwa kondisi berpacu tersebut tidak "nyata".
+Untuk menghindari kondisi "positif salah" ini pada saat pembangunan, kita
+mengimplementasikan
+https://golang.org/cl/6624059[versi yang tidak "berpacu"]
+yang dinyalakan hanya saat pendeteksi _data race_ berjalan.
+
+Akan tetapi beberapa bulan kemudian
+https://bradfitz.com/[Brad]
+menemui sebuah _bug_ yang
+https://golang.org/issue/4589[janggal dan menyebalkan].
+Setelah beberapa hari melakukan _debug_, dia menemukan kondisi berpacu yang
+nyata yang disebabkan oleh `ioutil.Discard`.
+
+Berikut kode yang diketahui berpacu dalam `io/ioutil`, yang mana `Discard`
+adalah `devNull` yang berbagi sebuah penyangga tunggal dengan semua
+penggunanya.
+
+----
+var blackHole [4096]byte // shared buffer
+
+func (devNull) ReadFrom(r io.Reader) (n int64, err error) {
+ readSize := 0
+ for {
+ readSize, err = r.Read(blackHole[:])
+ n += int64(readSize)
+ if err != nil {
+ if err == io.EOF {
+ return n, nil
+ }
+ return
+ }
+ }
+}
+----
+
+Program Brad memiliki sebuah tipe `trackDigestReader`, yang membungkus sebuah
+`io.Reader` dan mencatat _hash_ dari apa yang ia baca.
+
+----
+type trackDigestReader struct {
+ r io.Reader
+ h hash.Hash
+}
+
+func (t trackDigestReader) Read(p []byte) (n int, err error) {
+ n, err = t.r.Read(p)
+ t.h.Write(p[:n])
+ return
+}
+----
+
+Sebagai contoh, ia bisa digunakan untuk menghitung hash SHA-1 dari sebuah
+berkas saat membacanya:
+
+----
+tdr := trackDigestReader{r: file, h: sha1.New()}
+io.Copy(writer, tdr)
+fmt.Printf("File hash: %x", tdr.h.Sum(nil))
+----
+
+Pada kasus-kasus tertentu data terkadang tidak perlu ditulis--tetapi hash
+masih diperlukan--maka `Discard` digunakan:
+
+----
+io.Copy(ioutil.Discard, tdr)
+----
+
+Namun pada kasus ini penyangga `blackHole` bukan hanya lubang hitam; ia adalah
+tempat untuk menyimpan data antara pembacaan dari sumber `io.Reader` dan
+penulisan ke `hash.Hash`.
+Saat beberapa goroutine mulai melakukan _hash_ secara bersamaan, setiap
+goroutine akan berbagi penyangga `blackHole` yang sama, kondisi berpacu mulai
+timbul dengan mengkorupsi data antara pembacaan dan penulisan.
+Tidak ada eror atau panic yang terjadi, namun _hash_ yang dihasilkan selalu
+salah.
+
+----
+func (t trackDigestReader) Read(p []byte) (n int, err error) {
+ // penyangga p adalah blackHole
+ n, err = t.r.Read(p)
+ // p bisa dikorupsi oleh goroutine yang lain,
+ // baik oleh Read di atas atau oleh Write di bawah.
+ t.h.Write(p[:n])
+ return
+}
+----
+
+Bug ini akhirnya
+https://golang.org/cl/7011047[diperbaiki]
+dengan memberikan penyangga yang unik untuk setiap penggunaan
+`ioutil.Discard`, mengeliminasi kondisi berpacu pada penyangga yang berbagi.
+
+
+== Kesimpulan
+
+Pendeteksi _data race_ adalah perkakas yang tangguh untuk memeriksa ketepatan
+dari program yang konkuren.
+Ia tidak akan menimbulkan kondisi positif-salah, jadi perhatikan baik-baik
+peringatan yang dikeluarkan oleh pendeteksi ini.
+Namun ia hanya akan bekerja baik seperti halnya tes-tes Anda;
+Anda harus memastikan mereka benar-benar menggunakan properti konkuren dari
+kode Anda supaya pendeteksi _data race_ dapat melakukan kerjanya dengan baik.
+
+Apa yang Anda tunggu lagi?
+Jalankan "go test -race" pada kode Anda hari ini!
diff --git a/_content/blog/race-detector/race-fix.go b/_content/blog/race-detector/race-fix.go
new file mode 100644
index 0000000..a46ca3a
--- /dev/null
+++ b/_content/blog/race-detector/race-fix.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "fmt"
+ "math/rand"
+ "time"
+)
+
+func main() {
+ start := time.Now()
+ reset := make(chan bool)
+ var t *time.Timer
+ t = time.AfterFunc(randomDuration(), func() {
+ fmt.Println(time.Now().Sub(start))
+ reset <- true
+ })
+ for time.Since(start) < 5*time.Second {
+ <-reset
+ t.Reset(randomDuration())
+ }
+}
+
+func randomDuration() time.Duration {
+ return time.Duration(rand.Int63n(1e9))
+}
diff --git a/_content/blog/race-detector/race.go b/_content/blog/race-detector/race.go
new file mode 100644
index 0000000..e6c9df9
--- /dev/null
+++ b/_content/blog/race-detector/race.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+ "fmt"
+ "math/rand"
+ "time"
+)
+
+func main() {
+ start := time.Now()
+ var t *time.Timer
+ t = time.AfterFunc(randomDuration(), func() {
+ fmt.Println(time.Now().Sub(start))
+ t.Reset(randomDuration())
+ })
+ time.Sleep(5 * time.Second)
+}
+
+func randomDuration() time.Duration {
+ return time.Duration(rand.Int63n(1e9))
+}