Heim Backend-Entwicklung Golang Die zugrunde liegende Implementierung des Go-Funktionsabschlusses

Die zugrunde liegende Implementierung des Go-Funktionsabschlusses

Jul 25, 2023 pm 03:18 PM
go函数

Funktionsabschluss ist für die meisten Leser kein fortgeschrittenes Vokabular. Was ist also ein Abschluss? Hier ist ein Auszug aus der Definition im Wiki:

Ein Abschluss ist ein Datensatz, der eine Funktion zusammen mit einer Umgebung speichert. Die Umgebung ist eine Zuordnung, die jede freie Variable der Funktion verknüpft (Variablen, die lokal verwendet, aber in definiert werden). ein umschließender Bereich) mit dem Wert oder Verweis, an den der Name gebunden war, als der Abschluss erstellt wurde.

Kurz gesagt ist ein Abschluss eine Entität, die aus einer Funktion und einer Referenzumgebung besteht. Im Implementierungsprozess werden Abschlüsse häufig durch den Aufruf externer Funktionen und die Rückgabe ihrer internen Funktionen implementiert. Unter diesen bezieht sich die Referenzumgebung auf die Zuordnung freier Variablen in der externen Funktion (von der internen Funktion verwendet, aber in der externen Funktion definiert). Die interne Funktion führt externe freie Variablen ein, sodass diese Variablen nicht freigegeben oder gelöscht werden, selbst wenn sie die Umgebung der externen Funktion verlassen. Die zurückgegebene interne Funktion enthält weiterhin diese Informationen.

Die zugrunde liegende Implementierung des Go-Funktionsabschlusses

Diese Passage ist möglicherweise nicht leicht zu verstehen, also verwenden wir einfach ein Beispiel.

 1package main
 2
 3import "fmt"
 4
 5func outer() func() int {
 6    x := 1
 7    return func() int {
 8        x++
 9        return x
10    }
11}
12
13func main() {
14    closure := outer()
15    fmt.Println(closure())
16    fmt.Println(closure())
17}
18
19// output
202
213
Nach dem Login kopieren

Wie Sie sehen können, erleichtern zwei Funktionen in Go (Funktionen sind erstklassige Bürger und Unterstützung für anonyme Funktionen) die Implementierung von Abschlüssen.

Im obigen Beispiel <code style="font-size: inherit;line-height: inherit;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);"><span style="font-size: 15px;letter-spacing: 1px;">closure</span>是闭包函数,变量x就是引用环境,它们的组合就是闭包实体。<span style="font-size: 15px;letter-spacing: 1px;">x</span>本是<span style="font-size: 15px;letter-spacing: 1px;">outer</span>函数之内,匿名函数之外的局部变量。在正常函数调用结束之后,<span style="font-size: 15px;letter-spacing: 1px;">x</span>就会随着函数栈的销毁而销毁。但是由于匿名函数的引用,<span style="font-size: 15px;letter-spacing: 1px;">outer</span>返回的函数对象会一直持有<span style="font-size: 15px;letter-spacing: 1px;">x</span>变量。这造成了每次调用闭包<code style="font-size: inherit;line-height: inherit;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);"><span style="font-size: 15px;letter-spacing: 1px;">closure</span><span style="font-size: 15px;letter-spacing: 1px;">x</span>变量都会得到累加。

这里和普通的函数调用不一样:局部变量<span style="font-size: 15px;letter-spacing: 1px;">x</span>closure ist die Abschlussfunktion und die Variable x ist die Referenz Ihre Kombination ist eine Abschlusseinheit.

<p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">x<span style="font-size: 15px;letter-spacing: 1px;">
Das ist</p>outer🎜🎜Lokale Variablen innerhalb von Funktionen und außerhalb anonymer Funktionen. Nachdem der normale Funktionsaufruf beendet ist, 🎜🎜x🎜🎜 wird zerstört, wenn der Funktionsstapel zerstört wird. Aber aufgrund der Referenz der anonymen Funktion, 🎜🎜outer🎜🎜Das zurückgegebene Funktionsobjekt wird immer gehalten🎜 🎜x🎜🎜 Variable. Dies führt bei jedem Aufruf des Abschlusses zu 🎜🎜closure🎜🎜, 🎜🎜x🎜🎜Variablen werden akkumuliert. 🎜🎜🎜🎜Dies unterscheidet sich von gewöhnlichen Funktionsaufrufen: lokale Variablen🎜🎜x🎜🎜 folgt nicht der Funktion Anruf wird beendet und verschwindet. Warum ist das so? 🎜🎜🎜🎜🎜🎜🎜

实现原理

我们不妨从汇编入手,将上述代码稍微修改一下

 1package main
 2
 3func outer() func() int {
 4    x := 1
 5    return func() int {
 6        x++
 7        return x
 8    }
 9}
10
11func main() {
12    _ := outer()
13}
Nach dem Login kopieren

得到编译后的汇编语句如下。

 1$ go tool compile -S -N -l main.go 
 2"".outer STEXT size=181 args=0x8 locals=0x28
 3        0x0000 00000 (main.go:3)        TEXT    "".outer(SB), ABIInternal, $40-8
 4        ...
 5        0x0021 00033 (main.go:3)        MOVQ    $0, "".~r0+48(SP)
 6        0x002a 00042 (main.go:4)        LEAQ    type.int(SB), AX
 7        0x0031 00049 (main.go:4)        MOVQ    AX, (SP)
 8        0x0035 00053 (main.go:4)        PCDATA  $1, $0
 9        0x0035 00053 (main.go:4)        CALL    runtime.newobject(SB)
10        0x003a 00058 (main.go:4)        MOVQ    8(SP), AX
11        0x003f 00063 (main.go:4)        MOVQ    AX, "".&x+24(SP)
12        0x0044 00068 (main.go:4)        MOVQ    $1, (AX)
13        0x004b 00075 (main.go:5)        LEAQ    type.noalg.struct { F uintptr; "".x *int }(SB), AX
14        0x0052 00082 (main.go:5)        MOVQ    AX, (SP)
15        0x0056 00086 (main.go:5)        PCDATA  $1, $1
16        0x0056 00086 (main.go:5)        CALL    runtime.newobject(SB)
17        0x005b 00091 (main.go:5)        MOVQ    8(SP), AX
18        0x0060 00096 (main.go:5)        MOVQ    AX, ""..autotmp_4+16(SP)
19        0x0065 00101 (main.go:5)        LEAQ    "".outer.func1(SB), CX
20        0x006c 00108 (main.go:5)        MOVQ    CX, (AX)
21        ...
Nach dem Login kopieren

首先,我们发现不一样的是 <span style="font-size: 15px;letter-spacing: 1px;">x:=1</span> 会调用 <span style="font-size: 15px;letter-spacing: 1px;">runtime.newobject</span> 函数(内置<span style="font-size: 15px;letter-spacing: 1px;">new</span>函数的底层函数,它返回数据类型指针)。在正常函数局部变量的定义时,例如

 1package main
 2
 3func add() int {
 4    x := 100
 5    x++
 6    return x
 7}
 8
 9func main() {
10    _ = add()
11}
Nach dem Login kopieren

我们能发现 <span style="font-size: 15px;letter-spacing: 1px;">x:=100</span> 是不会调用 <span style="font-size: 15px;letter-spacing: 1px;">runtime.newobject</span> 函数的,它对应的汇编是如下

1"".add STEXT nosplit size=58 args=0x8 locals=0x10
2        0x0000 00000 (main.go:3)        TEXT    "".add(SB), NOSPLIT|ABIInternal, $16-8
3        ...
4        0x000e 00014 (main.go:3)        MOVQ    $0, "".~r0+24(SP)
5        0x0017 00023 (main.go:4)        MOVQ    $100, "".x(SP)  // x:=100
6        0x001f 00031 (main.go:5)        MOVQ    $101, "".x(SP)
7        0x0027 00039 (main.go:6)        MOVQ    $101, "".~r0+24(SP)
8        ...
Nach dem Login kopieren

留着疑问,继续往下看。我们发现有以下语句

1        0x004b 00075 (main.go:5)        LEAQ    type.noalg.struct { F uintptr; "".x *int }(SB), AX
2        0x0052 00082 (main.go:5)        MOVQ    AX, (SP)
3        0x0056 00086 (main.go:5)        PCDATA  $1, $1
4        0x0056 00086 (main.go:5)        CALL    runtime.newobject(SB)
Nach dem Login kopieren

我们看到 <span style="font-size: 15px;letter-spacing: 1px;">type.noalg.struct { F uintptr; "".x *int }(SB)</span>,这其实就是定义的一个闭包数据类型,它的结构表示如下

1type closure struct {
2    F uintptr   // 函数指针,代表着内部匿名函数
3    x *int      // 自由变量x,代表着对外部环境的引用
4}
Nach dem Login kopieren

之后,在通过 <span style="font-size: 15px;letter-spacing: 1px;">runtime.newobject</span> 函数创建了闭包对象。而且由于 <span style="font-size: 15px;letter-spacing: 1px;">LEAQ xxx yyy</span>代表的是将 <span style="font-size: 15px;letter-spacing: 1px;">xxx</span> 指针,传递给 <span style="font-size: 15px;letter-spacing: 1px;">yyy</span>,因此 <span style="font-size: 15px;letter-spacing: 1px;">outer</span> 函数最终的返回,其实是闭包结构体对象指针。在《详解逃逸分析》一文中,我们详细地描述了Go编译器的逃逸分析机制,对于这种函数返回暴露给外部指针的情况,很明显,闭包对象会被分配至堆上,变量x也会随着对象逃逸至堆。这就很好地解释了为什么<span style="font-size: 15px;letter-spacing: 1px;">x</span>变量没有随着函数栈的销毁而消亡。

我们可以通过逃逸分析来验证我们的结论

 1$  go build -gcflags &#39;-m -m -l&#39; main.go
 2# command-line-arguments
 3./main.go:6:3: outer.func1 capturing by ref: x (addr=true assign=true width=8)
 4./main.go:5:9: func literal escapes to heap:
 5./main.go:5:9:   flow: ~r0 = &{storage for func literal}:
 6./main.go:5:9:     from func literal (spill) at ./main.go:5:9
 7./main.go:5:9:     from return func literal (return) at ./main.go:5:2
 8./main.go:4:2: x escapes to heap:
 9./main.go:4:2:   flow: {storage for func literal} = &x:
10./main.go:4:2:     from func literal (captured by a closure) at ./main.go:5:9
11./main.go:4:2:     from x (reference) at ./main.go:6:3
12./main.go:4:2: moved to heap: x                   // 变量逃逸
13./main.go:5:9: func literal escapes to heap       // 函数逃逸
Nach dem Login kopieren

至此,我相信读者已经明白为什么闭包能持续持有外部变量的原因了。那么,我们来思考上文中留下的疑问,为什么在<span style="font-size: 15px;letter-spacing: 1px;">x:=1</span> 时会调用 <span style="font-size: 15px;letter-spacing: 1px;">runtime.newobject</span> 函数。

我们将上文中的例子改为如下,即删掉 <span style="font-size: 15px;letter-spacing: 1px;">x++</span> 代码

 1package main
 2
 3func outer() func() int {
 4    x := 1
 5    return func() int {
 6        return x
 7    }
 8}
 9
10func main() {
11    _ = outer()
12}
Nach dem Login kopieren

此时,<span style="font-size: 15px;letter-spacing: 1px;">x:=1</span>处的汇编代码,将不再调用 <span style="font-size: 15px;letter-spacing: 1px;">runtime.newobject</span> 函数,通过逃逸分析也会发现将<span style="font-size: 15px;letter-spacing: 1px;">x</span>不再逃逸,生成的闭包对象中的<span style="font-size: 15px;letter-spacing: 1px;">x</span>的将是值类型<span style="font-size: 15px;letter-spacing: 1px;">int</span>

1type closure struct {
2    F uintptr 
3    x int      
4}
Nach dem Login kopieren

这其实就是Go编译器做得精妙的地方:当闭包内没有对外部变量造成修改时,Go 编译器会将自由变量的引用传递优化为直接值传递,避免变量逃逸。


总结

函数闭包一点也不神秘,它就是函数和引用环境而组合的实体。在Go中,闭包在底层是一个结构体对象,它包含了函数指针与自由变量。

Der Escape-Analysemechanismus des Go-Compilers weist das Abschlussobjekt dem Heap zu, sodass die freie Variable nicht verschwindet, wenn der Funktionsstapel zerstört wird, und abhängig von der Abschlussentität immer vorhanden sein kann. Daher liegen die Vor- und Nachteile der Verwendung von Abschlüssen auf der Hand: Durch Abschlüsse kann die Verwendung globaler Variablen vermieden und stattdessen freie Variablen über einen längeren Zeitraum im Speicher gespeichert werden. Bei unsachgemäßer Verwendung kann dieses implizite Halten freier Variablen jedoch leicht zu Problemen führen Speicherverschwendung und -lecks.

In tatsächlichen Projekten gibt es nicht viele Nutzungsszenarien für Schließungen. Wenn Sie beispielsweise einen Abschluss in Ihren Code schreiben und eine von Ihnen geschriebene Rückruffunktion einen Abschluss bildet, müssen Sie natürlich vorsichtig sein, da sonst das Problem der Speichernutzung zu Problemen führen kann.

Das obige ist der detaillierte Inhalt vonDie zugrunde liegende Implementierung des Go-Funktionsabschlusses. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn

Heiße KI -Werkzeuge

Undresser.AI Undress

Undresser.AI Undress

KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover

AI Clothes Remover

Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool

Undress AI Tool

Ausziehbilder kostenlos

Clothoff.io

Clothoff.io

KI-Kleiderentferner

AI Hentai Generator

AI Hentai Generator

Erstellen Sie kostenlos Ai Hentai.

Heißer Artikel

R.E.P.O. Energiekristalle erklärten und was sie tun (gelber Kristall)
2 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌
Repo: Wie man Teamkollegen wiederbelebt
1 Monate vor By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island Abenteuer: Wie man riesige Samen bekommt
4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌

Heiße Werkzeuge

Notepad++7.3.1

Notepad++7.3.1

Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version

SublimeText3 chinesische Version

Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1

Senden Sie Studio 13.0.1

Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6

Dreamweaver CS6

Visuelle Webentwicklungstools

SublimeText3 Mac-Version

SublimeText3 Mac-Version

Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Schnellstart: Verwenden Sie Go-Sprachfunktionen, um einfache Bilderkennungsfunktionen zu implementieren Schnellstart: Verwenden Sie Go-Sprachfunktionen, um einfache Bilderkennungsfunktionen zu implementieren Jul 30, 2023 pm 09:49 PM

Schnellstart: Verwenden Sie Go-Sprachfunktionen, um einfache Bilderkennungsfunktionen zu implementieren. In der heutigen technologischen Entwicklung ist die Bilderkennungstechnologie zu einem heißen Thema geworden. Als schnelle und effiziente Programmiersprache verfügt die Go-Sprache über die Fähigkeit, Bilderkennungsfunktionen zu implementieren. Dieser Artikel bietet den Lesern eine Kurzanleitung zur Implementierung einfacher Bilderkennungsfunktionen mithilfe der Go-Sprachfunktionen. Zuerst müssen wir die Go-Sprachentwicklungsumgebung installieren. Sie können die entsprechende Version auf der offiziellen Website der Go-Sprache herunterladen (https://golang.org/).

Schnellstart: Verwenden Sie Go-Sprachfunktionen, um einfache Datenverschlüsselungs- und -entschlüsselungsfunktionen zu implementieren Schnellstart: Verwenden Sie Go-Sprachfunktionen, um einfache Datenverschlüsselungs- und -entschlüsselungsfunktionen zu implementieren Aug 03, 2023 am 11:29 AM

Schnellstart: Verwenden Sie Go-Sprachfunktionen, um einfache Datenverschlüsselungs- und -entschlüsselungsfunktionen zu implementieren. In der heutigen Informationsgesellschaft ist die Vertraulichkeit von Daten besonders wichtig geworden. Um die Vertraulichkeit der Daten zu gewährleisten, verwenden wir in der Regel verschiedene Verschlüsselungsalgorithmen zur Verschlüsselung der Daten. In diesem Artikel werden wir Go-Sprachfunktionen verwenden, um eine einfache Datenverschlüsselungs- und -entschlüsselungsfunktion zu implementieren. Zuerst müssen wir das Krypto-/Verschlüsselungspaket importieren, um den Verschlüsselungsalgorithmus verwenden zu können. Wir werden AES (AdvancedEncryptionS

Die zugrunde liegende Implementierung des Go-Funktionsabschlusses Die zugrunde liegende Implementierung des Go-Funktionsabschlusses Jul 25, 2023 pm 03:18 PM

Der Funktionsabschluss ist überhaupt nicht mysteriös. Es handelt sich um eine Einheit, die aus einer Funktion und einer Referenzumgebung besteht. In Go ist ein Abschluss ein Strukturobjekt unten, das Funktionszeiger und freie Variablen enthält.

Schnellstart: Verwenden Sie Go-Sprachfunktionen, um einfache Daten-Crawling-Funktionen zu implementieren Schnellstart: Verwenden Sie Go-Sprachfunktionen, um einfache Daten-Crawling-Funktionen zu implementieren Aug 01, 2023 pm 07:21 PM

Schnellstart: Verwenden Sie Go-Sprachfunktionen, um einfache Daten-Crawling-Funktionen zu implementieren. Im heutigen Internetzeitalter wird die Datenerfassung und -verarbeitung immer wichtiger. Als gängige Datenerfassungsmethode wird das Datencrawlen in verschiedenen Bereichen häufig eingesetzt. In diesem Artikel werde ich vorstellen, wie man mit Go-Sprachfunktionen eine einfache Daten-Crawling-Funktion implementiert, um den Lesern einen schnellen Einstieg zu erleichtern. Die Go-Sprache ist eine statisch stark typisierte Sprache. Aufgrund ihrer prägnanten Syntax und effizienten Parallelitätsleistung ist sie für viele Entwickler die erste Wahl. Im Folgenden wird erläutert, wie die Go-Sprachfunktion implementiert wird

Warum geben Funktionen in meinem Go-Programm falsche Werte zurück? Warum geben Funktionen in meinem Go-Programm falsche Werte zurück? Jun 10, 2023 pm 04:35 PM

In Go-Programmen ist der Rückgabewert einer Funktion sehr wichtig. Möglicherweise stoßen Sie auf Probleme, bei denen Ihre Funktion den falschen Wert zurückgibt oder keinen Wert zurückgibt, was zu Problemen mit Ihrem Programm führen kann. Diese Situation kann bei Programmen jeder Größe auftreten. In diesem Artikel werden einige der möglichen Ursachen dieser Probleme erläutert. Funktionsdefinitionsfehler Zunächst müssen Sie sicherstellen, dass Ihre Funktion korrekt definiert ist. Die Funktionsdefinition sollte den Funktionsnamen, die Parameterliste und den Rückgabetyp deklarieren. Wenn Sie den Rückgabetyp vergessen, kehrt Go standardmäßig zurück

So schreiben Sie umfassende Unit-Tests für Go-Funktionen So schreiben Sie umfassende Unit-Tests für Go-Funktionen May 02, 2024 pm 01:27 PM

Das Schreiben von Unit-Tests in Go trägt dazu bei, die Qualität und Zuverlässigkeit des Codes sicherzustellen. Unit-Tests umfassen Schritte wie das Importieren von Abhängigkeiten, das Einrichten von Objekten, das Definieren von Ein- und Ausgaben, das Aufrufen von Funktionen und das Durchsetzen von Ausgaben. Mithilfe der Assertion-Funktion aus dem Testpaket können Sie die tatsächliche Ausgabe mit der erwarteten Ausgabe vergleichen. Verwenden Sie den Befehl gotest, um Tests auszuführen und sicherzustellen, dass alle Tests erfolgreich sind, um die Genauigkeit des Go-Codes sicherzustellen.

Wie schreibe ich wartbare Golang-Funktionen effizient? Wie schreibe ich wartbare Golang-Funktionen effizient? Apr 12, 2024 pm 02:33 PM

Zu den wichtigsten Richtlinien zum Schreiben effizienter und wartbarer Go-Funktionen gehören: Halten Sie Funktionen kurz und prägnant, konzentrieren Sie sich auf eine einzelne Verantwortung, verwenden Sie klare Methodensignaturen, prüfen Sie auf Fehler und geben Sie klare Informationen zurück und verwenden Sie Dokumentationskommentare für Kommentare. Wenn Sie diese Richtlinien befolgen, entsteht Code, der klarer, einfacher zu testen und einfacher zu warten ist.

Wie unterscheiden sich PHP-Funktionen von Go-Funktionen? Wie unterscheiden sich PHP-Funktionen von Go-Funktionen? Apr 24, 2024 pm 03:51 PM

PHP- und Go-Funktionen weisen sowohl Ähnlichkeiten als auch wesentliche Unterschiede auf. Ähnlichkeiten: Verwenden Sie Namespaces und Bereiche, um Code zu organisieren. Parameter können als Wert oder Referenz übergeben werden. Gibt normalerweise einen oder mehrere Werte zurück. Unterschied: PHP verwendet ein dynamisches Typsystem, während Go ein statisches Typsystem verwendet. Go-Funktionen unterstützen die Verwendung von Standardwerten und variadischen Parametern, PHP hingegen nicht. Sowohl PHP als auch Go unterstützen anonyme Funktionen, die Syntax unterscheidet sich jedoch geringfügig.

See all articles