Berbagi teknologi

[Pertanyaan Wawancara] Golang (Bagian 4)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Daftar isi

1.Perbedaan antara buatan dan baru

2. Bagaimana cara menggunakan transfer nilai dan transfer alamat (transfer referensi) dalam bahasa Go? Apa bedanya?

3. Apa perbedaan array dan irisan dalam bahasa Go? Apa perbedaan antara array dan irisan saat meneruskan dalam bahasa Go? Bagaimana bahasa Go mengimplementasikan perluasan irisan?

4.Apa itu Go Sampaikan? Umumnya digunakan untuk apa?

5.Apa fungsi dan ciri-ciri penangguhan?

6. Implementasi yang mendasari Go Slice? Pergi ke mekanisme ekspansi irisan?

7. Apakah irisannya sama sebelum dan sesudah pemuaian?

8.Mengapa irisan tidak aman untuk thread?


1.Perbedaan antara buatan dan baru

Perbedaan: 1. Make hanya dapat digunakan untuk mengalokasikan dan menginisialisasi data bertipe slice, map, dan chan sedangkan new dapat mengalokasikan semua jenis data.

2. Alokasi baru mengembalikan sebuah pointer, yang merupakan tipe "*Type"; sementara make mengembalikan referensi, yang merupakan Type.

3. Ruang yang dialokasikan oleh new akan dikosongkan; setelah make mengalokasikan ruang, maka akan diinisialisasi.

2. Bagaimana cara menggunakan transfer nilai dan transfer alamat (transfer referensi) dalam bahasa Go? Apa bedanya?

Melewati nilai hanya akan menyalin nilai parameter dan memasukkannya ke dalam fungsi yang sesuai. Alamat kedua variabel berbeda dan tidak dapat mengubah satu sama lain.

Melewati alamat (melewati referensi) akan meneruskan variabel itu sendiri ke fungsi yang sesuai, dan isi nilai variabel dapat dimodifikasi dalam fungsi tersebut.

3. Apa perbedaan array dan irisan dalam bahasa Go? Apa perbedaan antara array dan irisan saat meneruskan dalam bahasa Go? Bagaimana bahasa Go mengimplementasikan perluasan irisan?

Himpunan:

Panjang array tetap Panjang array adalah bagian dari tipe array, jadi [3]int dan [4]int adalah dua tipe array yang berbeda. Array perlu menentukan ukurannya. Jika tidak ditentukan, ukurannya akan dihitung secara otomatis berdasarkan pasangan inisialisasi. Array tidak dapat diubah berdasarkan nilai

mengiris:

Panjang irisan dapat diubah. Irisan adalah struktur data yang ringan. Tiga atribut, penunjuk, panjang, dan kapasitas, tidak perlu menentukan ukurannya. Irisan dilewatkan berdasarkan alamat (melewati referensi) dan dapat diinisialisasi melalui array atau melalui fungsi bawaan make(). Selama inisialisasi, len=cap kemudian diperluas.

Array adalah tipe nilai dan irisan adalah tipe referensi;

Panjang array tetap, namun irisan tidak (irisan adalah array dinamis)

Lapisan yang mendasari irisan adalah array

Mekanisme perluasan irisan pada bahasa Go sangat cerdas. Ia mencapai perluasan dengan mengalokasikan ulang array yang mendasarinya dan memigrasikan data ke array baru. Khususnya, ketika sebuah irisan perlu diperluas, bahasa Go akan membuat array dasar baru dan menyalin data dalam array asli ke array baru. Kemudian, penunjuk irisan menunjuk ke larik baru, panjangnya diperbarui ke panjang asli ditambah panjang yang diperluas, dan kapasitas diperbarui ke panjang larik baru.

Untuk mencapai perluasan irisan:

pergi1.17

  1. // src/runtime/slice.go
  2. func growslice(et *_type, old slice, cap int) slice {
  3.     // ...
  4.     newcap := old.cap
  5.     doublecap := newcap + newcap
  6.     if cap > doublecap {
  7.         newcap = cap
  8.     } else {
  9.         if old.cap < 1024 {
  10.             newcap = doublecap
  11.         } else {
  12.             // Check 0 < newcap to detect overflow
  13.             // and prevent an infinite loop.
  14.             for 0 < newcap && newcap < cap {
  15.                 newcap += newcap / 4
  16.             }
  17.             // Set newcap to the requested cap when
  18.             // the newcap calculation overflowed.
  19.             if newcap <= 0 {
  20.                 newcap = cap
  21.             }
  22.         }
  23.     }
  24.     // ...
  25.     return slice{p, old.len, newcap}
  26. }

Sebelum mengalokasikan ruang memori, Anda perlu menentukan kapasitas irisan yang baru. Saat runtime, Anda dapat memilih strategi perluasan yang berbeda berdasarkan kapasitas irisan saat ini:

Jika kapasitas yang diharapkan lebih besar dari dua kali kapasitas saat ini, kapasitas yang diharapkan akan digunakan; jika panjang irisan saat ini kurang dari 1024, kapasitas akan digandakan jika panjang irisan saat ini lebih besar dari atau sama dengan 1024, kapasitas akan ditingkatkan sebesar 25% setiap kali hingga kapasitas baru Lebih besar dari kapasitas yang diharapkan;

pergi1.18

  1. // src/runtime/slice.go
  2. func growslice(et *_type, old slice, cap int) slice {
  3.     // ...
  4.     newcap := old.cap
  5.     doublecap := newcap + newcap
  6.     if cap > doublecap {
  7.         newcap = cap
  8.     } else {
  9.         const threshold = 256
  10.         if old.cap < threshold {
  11.             newcap = doublecap
  12.         } else {
  13.             // Check 0 < newcap to detect overflow
  14.             // and prevent an infinite loop.
  15.             for 0 < newcap && newcap < cap {
  16.                 // Transition from growing 2x for small slices
  17.                 // to growing 1.25x for large slices. This formula
  18.                 // gives a smooth-ish transition between the two.
  19.                 newcap += (newcap + 3*threshold) / 4
  20.             }
  21.             // Set newcap to the requested cap when
  22.             // the newcap calculation overflowed.
  23.             if newcap <= 0 {
  24.                 newcap = cap
  25.             }
  26.         }
  27.     }
  28.     // ...
  29.     return slice{p, old.len, newcap}
  30. }

Perbedaan dari versi sebelumnya terutama terletak pada ambang batas ekspansi dan baris kode ini:newcap += (newcap + 3*threshold) / 4

Sebelum mengalokasikan ruang memori, Anda perlu menentukan kapasitas irisan yang baru. Saat runtime, Anda dapat memilih strategi perluasan yang berbeda berdasarkan kapasitas irisan saat ini:

  • Jika kapasitas yang diharapkan lebih besar dari dua kali kapasitas saat ini, maka kapasitas yang diharapkan akan digunakan;

  • Jika panjang irisan saat ini kurang dari ambang batas (default 256), kapasitasnya akan menjadi dua kali lipat;

  • Jika panjang irisan saat ini lebih besar dari atau sama dengan ambang batas (default 256), kapasitas akan meningkat sebesar 25% setiap kali tolok ukurnya newcap + 3*threshold, sampai kapasitas baru lebih besar dari kapasitas yang diharapkan;

Ekspansi irisan dibagi menjadi dua tahap, sebelum dan sesudah go1.18:

1. Sebelum pergi 1.18:

  • Jika kapasitas yang diharapkan lebih besar dari dua kali kapasitas saat ini, maka kapasitas yang diharapkan akan digunakan;

  • Jika panjang irisan saat ini kurang dari 1024, kapasitasnya akan menjadi dua kali lipat;

  • Jika panjang irisan saat ini lebih besar dari 1024, kapasitas akan ditingkatkan sebesar 25% setiap kali hingga kapasitas baru lebih besar dari kapasitas yang diharapkan;

2. Setelah go1.18:

  • Jika kapasitas yang diharapkan lebih besar dari dua kali kapasitas saat ini, maka kapasitas yang diharapkan akan digunakan;

  • Jika panjang irisan saat ini kurang dari ambang batas (default 256), kapasitasnya akan menjadi dua kali lipat;

  • Jika panjang irisan saat ini lebih besar dari atau sama dengan ambang batas (default 256), kapasitas akan meningkat sebesar 25% setiap kali tolok ukurnya newcap + 3*threshold, sampai kapasitas baru lebih besar dari kapasitas yang diharapkan;

4.Apa itu Go Sampaikan? Umumnya digunakan untuk apa?

go menyampaikan adalah kerangka pengujian unit yang mendukung golang

go konveyor dapat secara otomatis memantau modifikasi file dan memulai pengujian, serta dapat menampilkan hasil pengujian ke antarmuka web secara real-time

go menyampaikan memberikan pernyataan yang kaya untuk menyederhanakan penulisan kasus uji

5.Apa fungsi dan ciri-ciri penangguhan?

Fungsi penundaan adalah:

Anda hanya perlu menambahkan kata kunci defer sebelum memanggil fungsi atau metode normal untuk melengkapi sintaks yang diperlukan untuk defer. Ketika pernyataan defer dijalankan, fungsi yang mengikuti defer akan ditangguhkan. Fungsi setelah penangguhan tidak akan dieksekusi sampai fungsi yang berisi pernyataan penangguhan dijalankan, terlepas dari apakah fungsi yang berisi pernyataan penangguhan berakhir secara normal melalui return atau berakhir secara tidak normal karena panik. Anda dapat menjalankan beberapa pernyataan defer dalam suatu fungsi, dengan urutan kebalikan dari deklarasinya.

Skenario umum untuk penundaan:

Pernyataan defer sering digunakan untuk menangani operasi berpasangan, seperti membuka, menutup, menghubungkan, melepaskan, mengunci, dan melepaskan kunci.

Melalui mekanisme penangguhan, betapa pun rumitnya logika fungsinya, sumber daya dapat dijamin akan dilepaskan di jalur eksekusi apa pun.

 

6. Implementasi yang mendasari Go Slice? Pergi ke mekanisme ekspansi irisan?

Pengirisan diimplementasikan berdasarkan array. Lapisan dasarnya adalah array. Lapisan itu sendiri sangat kecil dan dapat dipahami sebagai abstraksi dari array yang mendasarinya. Karena diimplementasikan berdasarkan array, memori yang mendasarinya dialokasikan secara terus menerus, sehingga sangat efisien. Data juga dapat diperoleh melalui indeks, dan dapat diulang serta pengumpulan sampah dioptimalkan. Irisan itu sendiri bukanlah array dinamis atau pointer array. Struktur data yang diimplementasikan secara internal mengacu pada array yang mendasarinya melalui sebuah pointer, dan atribut yang relevan diatur untuk membatasi operasi baca dan tulis data pada area tertentu. Irisan itu sendiri adalah objek read-only, dan mekanisme kerjanya mirip dengan enkapsulasi pointer array.

Objek irisan sangat kecil karena merupakan struktur data dengan hanya 3 bidang:

penunjuk ke array yang mendasarinya

panjang irisan

kapasitas irisan

Strategi pemotongan ekspansi di Go (1.17) adalah sebagai berikut:

Hakim pertama, jika kapasitas yang baru diterapkan lebih besar dari 2 kali kapasitas lama, maka kapasitas akhir adalah kapasitas yang baru diterapkan.

Sebaliknya, jika panjang irisan lama kurang dari 1024, kapasitas akhir akan menjadi dua kali lipat kapasitas lama.

Sebaliknya, jika panjang irisan lama lebih besar dari atau sama dengan 1024, kapasitas akhir akan ditingkatkan secara siklis sebesar 1/4 dari kapasitas lama hingga kapasitas akhir lebih besar atau sama dengan kapasitas yang baru diminta.

Jika nilai perhitungan kapasitas akhir melebihi kapasitas akhir, maka kapasitas akhir adalah kapasitas yang baru diminta.

7. Apakah irisannya sama sebelum dan sesudah pemuaian?

Situasi satu:

Array asli masih memiliki kapasitas yang dapat diperluas (kapasitas sebenarnya belum terisi). Dalam hal ini, array yang diperluas masih menunjuk ke array asli. Pengoperasian sebuah irisan dapat mempengaruhi beberapa pointer yang menunjuk ke alamat yang sama Mengiris.

Situasi kedua:

Ternyata kapasitas array telah mencapai nilai maksimum. Jika Anda ingin menambah kapasitas, Go akan membuka area memori terlebih dahulu secara default, menyalin nilai asli, lalu melakukan operasi append(). Situasi ini tidak mempengaruhi susunan aslinya sama sekali. Untuk menyalin Slice, sebaiknya gunakan fungsi Copy.

8.Mengapa irisan tidak aman untuk thread?

slice底层结构并没有使用加锁等方式,不支持并发读写,所以并不是线程安全的,
使用多个 goroutine 对类型为 slice 的变量进行操作,每次输出的值大概率都不会一样,与预期值不一致;
slice在并发执行中不会报错,但是数据会丢失
​
如果想实现slice线程安全,有两种方式:
​
方式一:通过加锁实现slice线程安全,适合对性能要求不高的场景。

  1. func TestSliceConcurrencySafeByMutex(t *testing.T) {
  2. var lock sync.Mutex //互斥锁
  3. a := make([]int, 0)
  4. var wg sync.WaitGroup
  5. for i := 0; i < 10000; i++ {
  6.  wg.Add(1)
  7.  go func(i int) {
  8.   defer wg.Done()
  9.   lock.Lock()
  10.   defer lock.Unlock()
  11.   a = append(a, i)
  12. }(i)
  13. }
  14. wg.Wait()
  15. t.Log(len(a))
  16. // equal 10000
  17. }
方式二:通过channel实现slice线程安全,适合对性能要求高的场景。
  1. func TestSliceConcurrencySafeByChanel(t *testing.T) {
  2. buffer := make(chan int)
  3. a := make([]int, 0)
  4. // 消费者
  5. go func() {
  6.  for v := range buffer {
  7.   a = append(a, v)
  8. }
  9. }()
  10. // 生产者
  11. var wg sync.WaitGroup
  12. for i := 0; i < 10000; i++ {
  13.  wg.Add(1)
  14.  go func(i int) {
  15.   defer wg.Done()
  16.   buffer <- i
  17. }(i)
  18. }
  19. wg.Wait()
  20. t.Log(len(a))
  21. // equal 10000
  22. }