Teknologian jakaminen

[Haastattelukysymys] Golang (osa 4)

2024-07-12

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

Sisällysluettelo

1. Ero merkin ja uuden välillä

2. Miten arvonsiirtoa ja osoitteensiirtoa (viitesiirtoa) käytetään Go-kielellä? Mitä eroa?

3. Mitä eroa on Go-kielen taulukoilla ja viipaloilla? Mitä eroa on taulukoiden ja viipaleiden välillä Go-kielellä välitettäessä? Miten Go-kieli toteuttaa slice-laajennuksen?

4. Mikä Go Convey on? Mihin sitä yleensä käytetään?

5. Mitkä ovat lykkäyksen toiminnot ja ominaisuudet?

6. Go slice:n taustalla oleva toteutus? Go slice laajennusmekanismi?

7. Ovatko viipaleet samat ennen ja jälkeen laajentamisen?

8. Miksi slice ei ole lankaturvallinen?


1. Ero merkin ja uuden välillä

Erot: 1. Make-toimintoa voidaan käyttää vain slice-, map- ja chan-tyyppisten tietojen allokointiin ja alustamiseen, kun taas uusi voi allokoida minkä tahansa tyyppisiä tietoja.

2. Uusi allokaatio palauttaa osoittimen, jonka tyyppi on "*Tyyppi", kun taas make palauttaa viitteen, joka on Tyyppi.

3. Uuden varaama tila tyhjennetään, kun make varaa tilan, se alustetaan.

2. Miten arvonsiirtoa ja osoitteensiirtoa (viitesiirtoa) käytetään Go-kielellä? Mitä eroa?

Arvon ohittaminen vain kopioi parametrin arvon ja sijoittaa sen vastaavaan funktioon. Näiden kahden muuttujan osoitteet ovat erilaisia, eivätkä ne voi muokata toisiaan.

Osoitteen välitys (lähetys viittauksella) välittää itse muuttujan vastaavaan funktioon ja muuttujan arvosisältöä voidaan muuttaa funktiossa.

3. Mitä eroa on Go-kielen taulukoilla ja viipaloilla? Mitä eroa on taulukoiden ja viipaleiden välillä Go-kielellä välitettäessä? Miten Go-kieli toteuttaa slice-laajennuksen?

Taulukko:

Taulukon kiinteä pituus Matriisipituus on osa taulukkotyyppiä, joten [3]int ja [4]int ovat kaksi eri taulukkotyyppiä. Jos sitä ei ole määritetty, koko lasketaan automaattisesti Alustusparia ei voi muuttaa arvolla

viipale:

Viipaleen pituutta voidaan muuttaa. Kolmen attribuutin, osoitin, pituus ja kapasiteetti, kokoa ei tarvitse määrittää. Viipaletta välitetään osoitteen mukaan (pass by reference) ja ne voidaan alustaa taulukon tai sisäänrakennetun make(-funktion) kautta Alustuksen aikana len=cap laajenee.

Taulukot ovat arvotyyppejä ja viipaleet viitetyyppejä;

Taulukon pituus on kiinteä, mutta siivut eivät ole (viipaleet ovat dynaamisia taulukoita)

Viipaleen alla oleva kerros on taulukko

Go-kielen slice-laajennusmekanismi on erittäin taitava. Erityisesti, kun siivua on laajennettava, Go-kieli luo uuden taustalla olevan taulukon ja kopioi alkuperäisen taulukon tiedot uuteen taulukkoon. Sitten viipalosoitin osoittaa uuteen taulukkoon, pituus päivitetään alkuperäiseen pituuteen plus laajennettu pituus ja kapasiteetti päivitetään uuden taulukon pituuteen.

Leikkeen laajeneminen:

mennä 1.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. }

Ennen kuin varaat muistitilan, sinun on määritettävä osion uusi kapasiteetti.

Jos odotettu kapasiteetti on suurempi kuin kaksinkertainen nykyinen kapasiteetti, käytetään odotettua kapasiteettia, jos nykyisen viipaleen pituus on alle 1024, kapasiteetti kaksinkertaistuu, jos nykyisen viipaleen pituus on suurempi tai yhtä suuri 1024, kapasiteettia lisätään 25 % joka kerta, kunnes uusi kapasiteetti Odotettua suurempi kapasiteetti;

mennä 1.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. }

Ero edelliseen versioon on pääasiassa laajennuskynnyksessä ja tässä koodirivissä:newcap += (newcap + 3*threshold) / 4

Ennen kuin varaat muistitilan, sinun on määritettävä osion uusi kapasiteetti.

  • Jos odotettu kapasiteetti on suurempi kuin kaksi kertaa nykyinen kapasiteetti, odotettua kapasiteettia käytetään;

  • Jos nykyisen viipaleen pituus on pienempi kuin kynnys (oletus 256), kapasiteetti kaksinkertaistuu;

  • Jos nykyisen viipaleen pituus on suurempi tai yhtä suuri kuin kynnys (oletus 256), kapasiteettia lisätään 25 % joka kerta newcap + 3*threshold, kunnes uusi kapasiteetti on suurempi kuin odotettu kapasiteetti;

Slice-laajennus on jaettu kahteen vaiheeseen, ennen go1.18:aa ja sen jälkeen:

1. Ennen lähtöä 1.18:

  • Jos odotettu kapasiteetti on suurempi kuin kaksi kertaa nykyinen kapasiteetti, odotettua kapasiteettia käytetään;

  • Jos nykyisen viipaleen pituus on pienempi kuin 1024, kapasiteetti kaksinkertaistuu;

  • Jos nykyisen viipaleen pituus on suurempi kuin 1024, kapasiteettia lisätään 25 % joka kerta, kunnes uusi kapasiteetti on suurempi kuin odotettu kapasiteetti;

2. Menon 1.18 jälkeen:

  • Jos odotettu kapasiteetti on suurempi kuin kaksi kertaa nykyinen kapasiteetti, odotettua kapasiteettia käytetään;

  • Jos nykyisen viipaleen pituus on pienempi kuin kynnys (oletus 256), kapasiteetti kaksinkertaistuu;

  • Jos nykyisen viipaleen pituus on suurempi tai yhtä suuri kuin kynnys (oletus 256), kapasiteettia lisätään 25 % joka kerta newcap + 3*threshold, kunnes uusi kapasiteetti on suurempi kuin odotettu kapasiteetti;

4. Mikä Go Convey on? Mihin sitä yleensä käytetään?

go convey on yksikkötestauskehys, joka tukee golangia

go convey voi automaattisesti valvoa tiedostojen muutoksia ja aloittaa testejä sekä lähettää testitulokset verkkokäyttöliittymään reaaliajassa

go convey tarjoaa monipuolisia väitteitä testitapausten kirjoittamisen yksinkertaistamiseksi

5. Mitkä ovat lykkäyksen toiminnot ja ominaisuudet?

Lykkäyksen tehtävä on:

Sinun tarvitsee vain lisätä avainsana lykätä ennen normaalin funktion tai menetelmän kutsumista lykkäämiseen vaaditun syntaksin suorittamiseksi. Kun lykkäyskäsky suoritetaan, lykkäystä seuraava toiminto lykätään. Defer-käskyn jälkeistä funktiota ei suoriteta ennen kuin defer-käskyn sisältävä funktio on suoritettu, riippumatta siitä, päättyykö defer-käskyn sisältävä funktio normaalisti returnin kautta vai päättyykö se epänormaalisti paniikin vuoksi. Voit suorittaa useita lykkäyskäskyjä funktiossa käänteisessä järjestyksessä niiden ilmoittamiselle.

Yleisiä lykkäysskenaarioita:

Defer-lausetta käytetään usein käsittelemään parillisia toimintoja, kuten avaamista, sulkemista, yhdistämistä, irrottamista, lukitsemista ja lukituksen vapauttamista.

Lykkäysmekanismin avulla, riippumatta siitä, kuinka monimutkainen toimintologiikka on, resurssien vapautuminen voidaan taata millä tahansa suorituspolulla.

 

6. Go slice:n taustalla oleva toteutus? Go slice laajennusmekanismi?

Viipalointi on toteutettu taulukoiden perusteella. Koska se on toteutettu taulukoiden pohjalta, sen taustalla olevaa muistia varataan jatkuvasti, mikä on erittäin tehokasta Dataa voidaan saada myös indeksien kautta, ja se voidaan iteroida ja optimoida roskien keräämistä. Leikkeet itsessään eivät ole dynaamisia taulukoita tai taulukkoosoittimia. Sisäisesti toteutettu tietorakenne viittaa alla olevaan taulukkoon osoittimen kautta, ja asiaankuuluvat attribuutit on asetettu rajoittamaan tietojen luku- ja kirjoitustoiminnot tietylle alueelle. Itse viipale on vain luku -objekti, ja sen toimintamekanismi on samanlainen kuin taulukon osoittimen kapselointi.

Slice-objekti on hyvin pieni, koska se on tietorakenne, jossa on vain 3 kenttää:

osoitin alla olevaan taulukkoon

viipaleen pituus

viipaleen kapasiteetti

Go (1.17) -laajennuksen viipalointistrategia on seuraava:

Ensin arvioi, jos uusi kapasiteetti on suurempi kuin 2 kertaa vanha kapasiteetti, lopullinen kapasiteetti on uusi kapasiteetti.

Muussa tapauksessa, jos vanhan siivun pituus on alle 1024, lopullinen kapasiteetti on kaksi kertaa vanha kapasiteetti.

Muussa tapauksessa, jos vanha viipaleen pituus on suurempi tai yhtä suuri kuin 1024, lopullista kapasiteettia lisätään syklisesti 1/4 vanhasta kapasiteetista, kunnes lopullinen kapasiteetti on suurempi tai yhtä suuri kuin uusi pyydetty kapasiteetti.

Jos lopullinen kapasiteettilaskennan arvo ylittyy, lopullinen kapasiteetti on uusi haettu kapasiteetti.

7. Ovatko viipaleet samat ennen ja jälkeen laajentamisen?

Tilanne yksi:

Alkuperäisessä taulukossa on edelleen kapasiteettia, jota voidaan laajentaa (todellista kapasiteettia ei ole täytetty. Tässä tapauksessa laajennettu taulukko osoittaa edelleen alkuperäiseen taulukkoon). Viipale.

Tilanne kaksi:

Osoittautuu, että taulukon kapasiteetti on saavuttanut maksimiarvon. Jos haluat laajentaa kapasiteettia, Go avaa ensin muistialueen, kopioi alkuperäisen arvon ja suorittaa sitten append()-toiminnon. Tämä tilanne ei vaikuta alkuperäiseen taulukkoon ollenkaan. Jos haluat kopioida osan, on parasta käyttää Copy-toimintoa.

8. Miksi slice ei ole lankaturvallinen?

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. }