le mie informazioni di contatto
Posta[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Sommario
1.La differenza tra marca e nuovo
4.Cos'è Go Convey? A cosa serve generalmente?
5.Quali sono le funzioni e le caratteristiche del differimento?
6.L'implementazione alla base di Go Slice? Vai al meccanismo di espansione delle fette?
7. Le fette sono le stesse prima e dopo l'espansione?
8.Perché Slice non è thread-safe?
Differenze: 1. Make può essere utilizzato solo per allocare e inizializzare dati di tipo slice, map e chan mentre new può allocare qualsiasi tipo di dati;
2. La nuova allocazione restituisce un puntatore, che è il tipo "*Type" mentre make restituisce un riferimento, che è Type.
3. Lo spazio allocato da new verrà cancellato; dopo che make avrà allocato lo spazio, verrà inizializzato.
Il passaggio per valore copierà solo il valore del parametro e lo inserirà nella funzione corrispondente. Gli indirizzi delle due variabili sono diversi e non possono modificarsi a vicenda.
Il passaggio dell'indirizzo (passaggio per riferimento) passerà la variabile stessa alla funzione corrispondente e il contenuto del valore della variabile potrà essere modificato nella funzione.
Vettore:
Lunghezza fissa dell'array La lunghezza dell'array fa parte del tipo di array, quindi [3]int e [4]int sono due tipi di array diversi. È necessario specificare la dimensione dell'array. Se non specificata, la dimensione verrà calcolata automaticamente in base a coppia di inizializzazione. L'array non può essere modificato viene passato per valore
fetta:
La lunghezza di una sezione può essere modificata. Una sezione è una struttura dati leggera, puntatore, lunghezza e capacità, non è necessario specificarne la dimensione. Le porzioni vengono passate per indirizzo (passaggio per riferimento) e possono essere inizializzate tramite un array o tramite la funzione incorporata make(). Durante l'inizializzazione, len=cap viene quindi espanso.
Le matrici sono tipi di valore e le sezioni sono tipi di riferimento;
La lunghezza degli array è fissa, ma le sezioni no (le sezioni sono array dinamici)
Lo strato sottostante della sezione è un array
Il meccanismo di espansione delle sezioni del linguaggio Go è molto intelligente: ottiene l'espansione riallocando l'array sottostante e migrando i dati in un nuovo array. Nello specifico, quando è necessario espandere una sezione, il linguaggio Go crea un nuovo array sottostante e copia i dati dell'array originale nel nuovo array. Quindi, il puntatore della sezione punta al nuovo array, la lunghezza viene aggiornata alla lunghezza originale più la lunghezza espansa e la capacità viene aggiornata alla lunghezza del nuovo array.
Per ottenere l'espansione della sezione:
andare1.17
- // src/runtime/slice.go
-
- func growslice(et *_type, old slice, cap int) slice {
- // ...
-
- newcap := old.cap
- doublecap := newcap + newcap
- if cap > doublecap {
- newcap = cap
- } else {
- if old.cap < 1024 {
- newcap = doublecap
- } else {
- // Check 0 < newcap to detect overflow
- // and prevent an infinite loop.
- for 0 < newcap && newcap < cap {
- newcap += newcap / 4
- }
- // Set newcap to the requested cap when
- // the newcap calculation overflowed.
- if newcap <= 0 {
- newcap = cap
- }
- }
- }
-
- // ...
-
- return slice{p, old.len, newcap}
- }
Prima di allocare lo spazio di memoria, è necessario determinare la nuova capacità della slice. In fase di esecuzione è possibile selezionare diverse strategie di espansione in base alla capacità attuale della slice:
Se la capacità prevista è maggiore del doppio della capacità attuale, verrà utilizzata la capacità prevista, se la lunghezza dello slice attuale è inferiore a 1024, la capacità verrà raddoppiata se la lunghezza dello slice attuale è maggiore o uguale; 1024, la capacità verrà aumentata ogni volta del 25% fino alla nuova capacità Capacità superiore a quella prevista;
andare1.18
- // src/runtime/slice.go
-
- func growslice(et *_type, old slice, cap int) slice {
- // ...
-
- newcap := old.cap
- doublecap := newcap + newcap
- if cap > doublecap {
- newcap = cap
- } else {
- const threshold = 256
- if old.cap < threshold {
- newcap = doublecap
- } else {
- // Check 0 < newcap to detect overflow
- // and prevent an infinite loop.
- for 0 < newcap && newcap < cap {
- // Transition from growing 2x for small slices
- // to growing 1.25x for large slices. This formula
- // gives a smooth-ish transition between the two.
- newcap += (newcap + 3*threshold) / 4
- }
- // Set newcap to the requested cap when
- // the newcap calculation overflowed.
- if newcap <= 0 {
- newcap = cap
- }
- }
- }
-
- // ...
-
- return slice{p, old.len, newcap}
- }
-
La differenza rispetto alla versione precedente sta principalmente nella soglia di espansione e in questa riga di codice:newcap += (newcap + 3*threshold) / 4
。
Prima di allocare lo spazio di memoria, è necessario determinare la nuova capacità della slice. In fase di esecuzione è possibile selezionare diverse strategie di espansione in base alla capacità attuale della slice:
Se la capacità prevista è superiore al doppio della capacità attuale, verrà utilizzata la capacità prevista;
Se la lunghezza della slice corrente è inferiore alla soglia (default 256), la capacità verrà raddoppiata;
Se la lunghezza della sezione corrente è maggiore o uguale alla soglia (predefinita 256), la capacità verrà aumentata del 25% ogni volta newcap + 3*threshold
, finché la nuova capacità non sarà maggiore della capacità prevista;
L'espansione degli slice è divisa in due fasi, prima e dopo go1.18:
1. Prima dell'1.18:
Se la capacità prevista è superiore al doppio della capacità attuale, verrà utilizzata la capacità prevista;
Se la lunghezza della sezione corrente è inferiore a 1024, la capacità verrà raddoppiata;
Se la lunghezza della sezione corrente è maggiore di 1024, la capacità verrà aumentata ogni volta del 25% finché la nuova capacità non sarà maggiore della capacità prevista;
2. Dopo l'1.18:
Se la capacità prevista è superiore al doppio della capacità attuale, verrà utilizzata la capacità prevista;
Se la lunghezza della slice corrente è inferiore alla soglia (default 256), la capacità verrà raddoppiata;
Se la lunghezza della sezione corrente è maggiore o uguale alla soglia (predefinita 256), la capacità verrà aumentata del 25% ogni volta newcap + 3*threshold
, finché la nuova capacità non sarà maggiore della capacità prevista;
go convogliare è un framework di test unitario che supporta Golang
go trasmettere può monitorare automaticamente le modifiche ai file e avviare i test e può inviare i risultati dei test all'interfaccia web in tempo reale
go trasmettere fornisce ricche asserzioni per semplificare la scrittura dei casi di test
La funzione del differimento è:
È sufficiente aggiungere la parola chiave defer prima di chiamare una normale funzione o metodo per completare la sintassi richiesta per defer. Quando viene eseguita l'istruzione defer, la funzione successiva al defer verrà rinviata. La funzione dopo il defer non verrà eseguita finché non viene eseguita la funzione contenente l'istruzione defer, indipendentemente dal fatto che la funzione contenente l'istruzione defer termini normalmente tramite return o termini in modo anomalo a causa di panico. È possibile eseguire più istruzioni di differimento in una funzione, nell'ordine inverso rispetto alla loro dichiarazione.
Scenari comuni per il differimento:
L'istruzione defer viene spesso utilizzata per gestire operazioni abbinate, come apertura, chiusura, connessione, disconnessione, blocco e rilascio dei blocchi.
Attraverso il meccanismo di differimento, non importa quanto complessa sia la logica della funzione, è possibile garantire che le risorse vengano rilasciate in qualsiasi percorso di esecuzione.
L'affettamento è implementato in base agli array. Il suo livello sottostante è un array. Esso stesso è molto piccolo e può essere inteso come un'astrazione dell'array sottostante. Poiché è implementato in base agli array, la memoria sottostante viene allocata in modo continuo, il che è molto efficiente. I dati possono essere ottenuti anche tramite indici e possono essere iterati e ottimizzati per la raccolta dei rifiuti. Le porzioni stesse non sono array dinamici o puntatori di array. La struttura dati implementata internamente fa riferimento all'array sottostante tramite un puntatore e gli attributi rilevanti sono impostati per limitare le operazioni di lettura e scrittura dei dati a un'area specifica. La slice stessa è un oggetto di sola lettura e il suo meccanismo di funzionamento è simile all'incapsulamento di un puntatore ad array.
L'oggetto slice è molto piccolo perché è una struttura dati con solo 3 campi:
puntatore all'array sottostante
lunghezza della fetta
capacità delle fette
La strategia per suddividere l'espansione in Go (1.17) è la seguente:
Primo giudice, se la nuova capacità applicata è maggiore di 2 volte la vecchia capacità, la capacità finale sarà la nuova capacità applicata.
Altrimenti, se la lunghezza della vecchia sezione è inferiore a 1024, la capacità finale sarà doppia rispetto alla vecchia capacità.
Diversamente, se la lunghezza della vecchia slice è maggiore o uguale a 1024, la capacità finale verrà incrementata ciclicamente di 1/4 dalla vecchia capacità finché la capacità finale non sarà maggiore o uguale alla nuova capacità richiesta.
Se il valore del calcolo della capacità finale eccede, la capacità finale sarà la nuova capacità richiesta.
Situazione uno:
L'array originale ha ancora una capacità che può essere espansa (la capacità effettiva non è stata riempita). In questo caso, l'array espanso punta ancora all'array originale. L'operazione di una sezione può influenzare più puntatori che puntano allo stesso indirizzo del file Fetta.
Situazione due:
Risulta che la capacità dell'array ha raggiunto il valore massimo. Se desideri espandere la capacità, Go aprirà prima un'area di memoria per impostazione predefinita, copierà il valore originale e quindi eseguirà l'operazione append(). Questa situazione non influisce affatto sull'array originale. Per copiare una Slice è meglio utilizzare la funzione Copia.
slice底层结构并没有使用加锁等方式,不支持并发读写,所以并不是线程安全的, 使用多个 goroutine 对类型为 slice 的变量进行操作,每次输出的值大概率都不会一样,与预期值不一致; slice在并发执行中不会报错,但是数据会丢失 如果想实现slice线程安全,有两种方式: 方式一:通过加锁实现slice线程安全,适合对性能要求不高的场景。
- func TestSliceConcurrencySafeByMutex(t *testing.T) {
- var lock sync.Mutex //互斥锁
- a := make([]int, 0)
- var wg sync.WaitGroup
- for i := 0; i < 10000; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- lock.Lock()
- defer lock.Unlock()
- a = append(a, i)
- }(i)
- }
- wg.Wait()
- t.Log(len(a))
- // equal 10000
- }
-
方式二:通过channel实现slice线程安全,适合对性能要求高的场景。
- func TestSliceConcurrencySafeByChanel(t *testing.T) {
- buffer := make(chan int)
- a := make([]int, 0)
- // 消费者
- go func() {
- for v := range buffer {
- a = append(a, v)
- }
- }()
- // 生产者
- var wg sync.WaitGroup
- for i := 0; i < 10000; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- buffer <- i
- }(i)
- }
- wg.Wait()
- t.Log(len(a))
- // equal 10000
- }