2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Inhaltsverzeichnis
1. Der Unterschied zwischen Marke und Neu
4.Was ist GoConvey? Wofür wird es im Allgemeinen verwendet?
5.Was sind die Funktionen und Merkmale von Defer?
6. Die zugrunde liegende Implementierung von Go Slice? Go-Slice-Erweiterungsmechanismus?
7. Sind die Slices vor und nach der Erweiterung gleich?
8.Warum ist Slice nicht threadsicher?
Unterschiede: 1. Make kann nur zum Zuweisen und Initialisieren von Daten der Typen Slice, Map und Chan verwendet werden, während new jeden Datentyp zuordnen kann.
2. Die neue Zuweisung gibt einen Zeiger vom Typ „*Type“ zurück, während make eine Referenz vom Typ „Type“ zurückgibt.
3. Der von new zugewiesene Speicherplatz wird gelöscht. Nachdem make den Speicherplatz zugewiesen hat, wird er initialisiert.
Durch die Wertübergabe wird nur der Wert des Parameters kopiert und in die entsprechende Funktion eingefügt. Die Adressen der beiden Variablen sind unterschiedlich und können sich nicht gegenseitig ändern.
Durch die Adressübergabe (Übergabe als Referenz) wird die Variable selbst an die entsprechende Funktion übergeben, und der Wertinhalt der Variablen kann in der Funktion geändert werden.
Array:
Feste Array-Länge Die Array-Länge ist Teil des Array-Typs, daher sind [3]int und [4]int zwei verschiedene Array-Typen. Wenn nicht angegeben, wird die Größe automatisch berechnet Das Initialisierungspaar kann nicht geändert werden und wird als Wert übergeben
Scheibe:
Die Länge eines Slice kann geändert werden. Die drei Attribute Zeiger, Länge und Kapazität müssen nicht angegeben werden. Slices werden per Adresse übergeben (übergeben als Referenz) und können über ein Array oder über die integrierte Funktion make() initialisiert werden. Während der Initialisierung wird len=cap dann erweitert.
Arrays sind Werttypen und Slices sind Referenztypen;
Die Länge von Arrays ist fest, Slices jedoch nicht (Slices sind dynamische Arrays).
Die zugrunde liegende Schicht des Slice ist ein Array
Der Slice-Erweiterungsmechanismus der Go-Sprache ist sehr clever. Er erreicht die Erweiterung durch Neuzuweisung des zugrunde liegenden Arrays und Migration von Daten in ein neues Array. Insbesondere wenn ein Slice erweitert werden muss, erstellt die Go-Sprache ein neues zugrunde liegendes Array und kopiert die Daten im ursprünglichen Array in das neue Array. Dann zeigt der Slice-Zeiger auf das neue Array, die Länge wird auf die ursprüngliche Länge plus die erweiterte Länge aktualisiert und die Kapazität wird auf die Länge des neuen Arrays aktualisiert.
So erreichen Sie eine Slice-Erweiterung:
weiter1.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}
- }
Bevor Sie Speicherplatz zuweisen, müssen Sie die neue Slice-Kapazität bestimmen. Zur Laufzeit können Sie basierend auf der aktuellen Kapazität des Slice verschiedene Strategien zur Erweiterung auswählen:
Wenn die erwartete Kapazität größer als das Doppelte der aktuellen Kapazität ist, wird die erwartete Kapazität verwendet. Wenn die Länge des aktuellen Slice kleiner als 1024 ist, wird die Kapazität verdoppelt, wenn die Länge des aktuellen Slice größer oder gleich ist 1024, die Kapazität wird jedes Mal um 25 % erhöht, bis die neue Kapazität größer als die erwartete Kapazität ist;
weiter1.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}
- }
-
Der Unterschied zur Vorgängerversion liegt hauptsächlich im Erweiterungsschwellenwert und dieser Codezeile:newcap += (newcap + 3*threshold) / 4
。
Bevor Sie Speicherplatz zuweisen, müssen Sie die neue Slice-Kapazität bestimmen. Zur Laufzeit können Sie basierend auf der aktuellen Kapazität des Slice verschiedene Strategien zur Erweiterung auswählen:
Wenn die erwartete Kapazität mehr als das Doppelte der aktuellen Kapazität beträgt, wird die erwartete Kapazität verwendet.
Wenn die Länge des aktuellen Slice kleiner als der Schwellenwert (Standard 256) ist, wird die Kapazität verdoppelt;
Wenn die Länge des aktuellen Slice größer oder gleich dem Schwellenwert ist (Standard 256), wird die Kapazität jedes Mal um 25 % erhöht newcap + 3*threshold
, bis die neue Kapazität größer als die erwartete Kapazität ist;
Die Slice-Erweiterung ist in zwei Phasen unterteilt, vor und nach go1.18:
1. Vor go1.18:
Wenn die erwartete Kapazität mehr als das Doppelte der aktuellen Kapazität beträgt, wird die erwartete Kapazität verwendet.
Wenn die Länge des aktuellen Slice weniger als 1024 beträgt, wird die Kapazität verdoppelt;
Wenn die Länge des aktuellen Slice größer als 1024 ist, wird die Kapazität jedes Mal um 25 % erhöht, bis die neue Kapazität größer als die erwartete Kapazität ist;
2. Nach go1.18:
Wenn die erwartete Kapazität mehr als das Doppelte der aktuellen Kapazität beträgt, wird die erwartete Kapazität verwendet.
Wenn die Länge des aktuellen Slice kleiner als der Schwellenwert (Standard 256) ist, wird die Kapazität verdoppelt;
Wenn die Länge des aktuellen Slice größer oder gleich dem Schwellenwert ist (Standard 256), wird die Kapazität jedes Mal um 25 % erhöht newcap + 3*threshold
, bis die neue Kapazität größer als die erwartete Kapazität ist;
Go Convey ist ein Unit-Test-Framework, das Golang unterstützt
go conveyer kann Dateiänderungen automatisch überwachen, Tests starten und Testergebnisse in Echtzeit an die Weboberfläche ausgeben
go convey bietet umfangreiche Aussagen, um das Schreiben von Testfällen zu vereinfachen
Die Funktion von defer ist:
Sie müssen lediglich das Schlüsselwort defer hinzufügen, bevor Sie eine normale Funktion oder Methode aufrufen, um die für defer erforderliche Syntax zu vervollständigen. Wenn die Defer-Anweisung ausgeführt wird, wird die auf den Defer folgende Funktion zurückgestellt. Die Funktion nach dem Defer wird erst dann ausgeführt, wenn die Funktion mit der Defer-Anweisung ausgeführt wird, unabhängig davon, ob die Funktion mit der Defer-Anweisung normal durch Return endet oder aufgrund einer Panik abnormal endet. Sie können in einer Funktion mehrere Defer-Anweisungen in umgekehrter Reihenfolge ihrer Deklaration ausführen.
Häufige Szenarien für die Verzögerung:
Die Defer-Anweisung wird häufig verwendet, um gepaarte Vorgänge wie Öffnen, Schließen, Verbinden, Trennen, Sperren und Freigeben von Sperren abzuwickeln.
Durch den Verzögerungsmechanismus kann unabhängig von der Komplexität der Funktionslogik garantiert werden, dass Ressourcen in jedem Ausführungspfad freigegeben werden.
Das Slicing wird basierend auf Arrays implementiert. Die zugrunde liegende Ebene ist ein Array. Es ist selbst sehr klein und kann als Abstraktion des zugrunde liegenden Arrays verstanden werden. Da es auf Arrays basiert, wird der zugrunde liegende Speicher kontinuierlich zugewiesen, was sehr effizient ist. Daten können auch durch Indizes, Iteration und Speicherbereinigungsoptimierung abgerufen werden. Slices selbst sind keine dynamischen Arrays oder Array-Zeiger. Die intern implementierte Datenstruktur verweist über einen Zeiger auf das zugrunde liegende Array, und relevante Attribute werden festgelegt, um Datenlese- und -schreibvorgänge auf einen bestimmten Bereich zu beschränken. Das Slice selbst ist ein schreibgeschütztes Objekt und sein Arbeitsmechanismus ähnelt der Kapselung eines Array-Zeigers.
Das Slice-Objekt ist sehr klein, da es eine Datenstruktur mit nur 3 Feldern ist:
Zeiger auf das zugrunde liegende Array
Scheibenlänge
Scheibenkapazität
Die Strategie zur Slicing-Erweiterung in Go (1.17) lautet wie folgt:
Erster Richter: Wenn die neu angewendete Kapazität mehr als das Zweifache der alten Kapazität beträgt, ist die endgültige Kapazität die neu angewendete Kapazität.
Andernfalls beträgt die endgültige Kapazität das Doppelte der alten Kapazität, wenn die Länge des alten Slice weniger als 1024 beträgt.
Andernfalls, wenn die alte Slice-Länge größer oder gleich 1024 ist, wird die endgültige Kapazität zyklisch um 1/4 der alten Kapazität erhöht, bis die endgültige Kapazität größer oder gleich der neu angeforderten Kapazität ist.
Wenn der endgültige Kapazitätsberechnungswert überläuft, ist die endgültige Kapazität die neu angeforderte Kapazität.
Situation eins:
Das ursprüngliche Array verfügt weiterhin über erweiterbare Kapazität (die tatsächliche Kapazität wurde nicht gefüllt). In diesem Fall zeigt das erweiterte Array weiterhin auf das ursprüngliche Array, und die Operation an einem Slice kann sich auf mehrere Zeiger auswirken, die auf dieselbe Adresse zeigen die Scheibe.
Situation zwei:
Es stellt sich heraus, dass die Kapazität des Arrays den Maximalwert erreicht hat. Wenn Sie die Kapazität erweitern möchten, öffnet Go standardmäßig zunächst einen Speicherbereich, kopiert den ursprünglichen Wert und führt dann die Operation append() aus. Diese Situation hat keinerlei Auswirkungen auf das ursprüngliche Array. Um ein Slice zu kopieren, verwenden Sie am besten die Kopierfunktion.
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
- }