Heim Backend-Entwicklung Golang Detaillierte Erläuterung der Verwendung von Goroutine in der Go-Sprache

Detaillierte Erläuterung der Verwendung von Goroutine in der Go-Sprache

Nov 25, 2019 pm 02:34 PM
go语言

Detaillierte Erläuterung der Verwendung von Goroutine in der Go-Sprache

goroutine in go ist eine Funktion von go language, die Parallelität auf Sprachebene unterstützt. Als ich zum ersten Mal mit Go in Kontakt kam, war ich mit Gos Goroutine äußerst zufrieden. Es ist so einfach, Parallelität zu implementieren, dass es fast lächerlich ist.

Während des Projektprozesses stellte ich jedoch zunehmend fest, dass Goroutine eine Sache ist, die leicht von jedem missbraucht werden kann. Goroutine ist ein zweischneidiges Schwert. Hier sind ein paar Sünden bei der Verwendung von Goroutine:

1. Die Zeigerübergabe in Goroutine ist unsicher

fun main() {
    request := request.NewRequest() //这里的NewRequest()是传递回一个type Request的指针
    go saveRequestToRedis1(request)
    go saveReuqestToRedis2(request)
     
    select{}
 
}
Nach dem Login kopieren

Sehr logischer Code:

Die Hauptroutine öffnet eine Routine, um die Anfrage an saveRequestToRedis1 weiterzuleiten, und lässt sie die Anfrage im Redis-Knoten 1 speichern.

Öffnen Sie gleichzeitig eine andere Routine, um die Anfrage an saveReuqestToRedis2 zu übergeben, und lassen Sie sie Speichern Sie die Anfrage. Gehen Sie zu Redis-Knoten 2

Dann tritt die Hauptroutine in die Schleife ein (ohne den Prozess zu beenden)

Jetzt kommt das Problem: Die Funktionen saveRequestToRedis1 und saveReuqestToRedis2 wurden nicht von mir geschrieben. Aber von einem anderen Mitglied des Teams. Ich weiß nichts über die Implementierung und möchte nicht näher auf die spezifische interne Implementierung eingehen. Aber dem Funktionsnamen zufolge habe ich ihn als selbstverständlich angesehen und den Anforderungszeiger übergeben.

Tatsächlich werden saveRequestToRedis1 und saveRequestToRedis2 wie folgt implementiert:

func saveRequestToRedis1(request *Request){
     …
     request.ToUsers = []int{1,2,3} //这里是一个赋值操作,修改了request指向的数据结构
     …
    redis.Save(request)
    return
}
Nach dem Login kopieren

Was ist daran falsch? Die beiden Goroutinen saveRequestToRedis1 und saveReuqestToRedis2 ändern dieselbe gemeinsame Datenstruktur. Da die Ausführung der Routine jedoch ungeordnet ist, können wir nicht garantieren, dass die Einstellung „request.ToUsers“ und redis.Save() eine atomare Operation sind, sodass die tatsächliche Speicherung von Redis erfolgt Es wird ein Datenfehlerfehler auftreten.

Nun, man kann sagen, dass es ein Problem mit der Implementierung dieser saveRequestToRedis-Funktion gibt und Sie nicht daran gedacht haben, dass sie mit der Go-Routine aufgerufen wird. Bitte denken Sie noch einmal darüber nach. Es gibt kein Problem mit der spezifischen Implementierung von saveRequestToRedis. Es sollte nicht berücksichtigt werden, wie die obere Ebene es verwendet.

Das heißt, es gibt ein Problem bei der Verwendung meiner Goroutine. Als die Hauptroutine eine Routine öffnete, wurde nicht bestätigt, ob ein Code in der Routine die Daten in der Hauptroutine geändert hat. Ja, die Hauptroutine muss diese Situation berücksichtigen.

Wenn die Haupt-Goroutine die Go-Routine aktiviert, muss sie jede Codezeile in der Unterroutine lesen, um festzustellen, ob die gemeinsam genutzten Daten geändert wurden? ? Wie sehr verlangsamt dies die Entwicklungsgeschwindigkeit im eigentlichen Projektentwicklungsprozess!

Die Go-Sprache verwendet Goroutine, um den Druck der gleichzeitigen Entwicklung zu verringern, hätte aber nie gedacht, dass dies andererseits den Entwicklungsdruck erhöhen würde.

Alles, was oben gesagt wurde, soll eine Schlussfolgerung ziehen:

Das Weitergeben des Gorotine-Zeigers ist unsicher! !

Wenn das vorherige Beispiel nicht subtil genug ist, hier ist ein weiteres Beispiel:

fun (this *Request)SaveRedis() {
    redis1 := redis.NewRedisAddr("xxxxxx")
    redis2 := redis.NewRedisAddr("xxxxxx")
    go this.saveRequestToRedis(redis1)
    go this.saveRequestToRedis(redis2)
     
    select{}
}
Nach dem Login kopieren

Nur ​​wenige Leute werden darüber nachdenken, ob es ein Problem mit dem Objekt gibt, auf das dieser Zeiger zeigt an die Routine übergeben Es sollte gesagt werden, dass es sehr versteckt ist.

2. Goroutine erhöht den Risikofaktor der Funktion

Dies ergibt sich tatsächlich aus dem obigen Punkt. Wie oben erwähnt, ist es unsicher, einen Zeiger an eine Go-Funktion zu übergeben. Betrachten Sie es also aus einem anderen Blickwinkel: Wie können Sie garantieren, dass die Funktion, die Sie aufrufen möchten, nicht in der Funktionsimplementierung enthalten ist? Wenn Sie sich die spezifische Implementierung im Funktionskörper nicht ansehen, gibt es keine Möglichkeit, sie zu bestimmen.

Wenn wir zum Beispiel das obige typische Beispiel leicht ändern

func main() {
    request := request.NewRequest()
    saveRequestToRedis1(request)
    saveRequestToRedis2(request)
    select{}
}
Nach dem Login kopieren

Da wir jetzt keine Parallelität verwenden, wird dieses Problem definitiv nicht auftreten, oder? Ich bin in die Funktion gejagt und war verblüfft:

func saveReqeustToRedis1(request *Request) {
           …
            go func() {
          …
          request.ToUsers = []{1,2,3}
         ….
         redis.Save(request)
    }
}
Nach dem Login kopieren

hat eine Goroutine erstellt und das Objekt geändert, auf das der Anforderungszeiger zeigt. Hier ist ein Fehler aufgetreten. Nun, wenn Sie beim Aufrufen der Funktion nicht auf die spezifische Implementierung innerhalb der Funktion achten, lässt sich dieses Problem nicht vermeiden.

Im schlimmsten Fall ist also jeder Funktionsaufruf theoretisch unsicher! Stellen Sie sich vor, diese Aufruffunktion wäre nicht von jemandem aus meinem eigenen Entwicklungsteam geschrieben worden, sondern würde Open-Source-Code von Drittanbietern im Internet verwenden ... Ich kann mir wirklich nicht vorstellen, wie lange es dauern würde, diesen Fehler zu finden.

3. Goroutine-Missbrauchsfalle

Sehen Sie sich dieses Beispiel an:

func main() {
    go saveRequestToRedises(request)
}
 
func saveRequestToRedieses(request *Request) {
    for _, redis := range Redises {
        go redis.saveRequestToRedis(request)
    }
}
 
func saveRequestToRedis(request *Request) {
            ….
            go func() {
                     request.ToUsers = []{1,2,3}
                        …
                        redis.Save(request)
            }
 
}
Nach dem Login kopieren

Es ist erstaunlich, Goroutine ist überall, es schien im Handumdrehen aus dem Nichts auftauchen. Das ist der Missbrauch von go. Wir sehen es überall, aber es ist nicht ganz klar, wo wir go verwenden sollen. Warum Go verwenden? Wird Goroutine wirklich die Effizienz verbessern?

Parallelität in der C-Sprache ist viel komplizierter und umständlicher als die Parallelität in der Go-Sprache, daher werden wir vor der Verwendung gründlich darüber nachdenken und die Vor- und Nachteile der Verwendung von Parallelität abwägen.

Wie man damit umgeht

Hier sind ein paar Möglichkeiten, wie ich mit diesen Problemen umgehe:

1 . Wenn Sie eine Goroutine verwenden und eine Funktion einen Zeiger übergeben muss, die Funktionsebene jedoch sehr tief ist und die Sicherheit nicht gewährleistet werden kann, übergeben Sie den Zeiger an einen Klon des Objekts, anstatt den Zeiger direkt zu übergeben

fun main() {
    request := request.NewRequest()
    go saveRequestToRedis1(request.Clone())
    go saveReuqestToRedis2(request.Clone())
     
    select{}
 
}
Nach dem Login kopieren

Die Klonfunktion muss separat geschrieben werden. Sie können dieser Methode einfach folgen, nachdem die Struktur definiert wurde. Zum Beispiel:

func (this *Request)Clone(){
    newRequest := NewRequst()
    newRequest.ToUsers = make([]int, len(this.ToUsers))
    copy(newRequest.ToUsers, this.ToUsers)
 
}
Nach dem Login kopieren

其实从效率角度考虑这样确实会产生不必要的Clone的操作,耗费一定内存和CPU。但是在我看来,首先,为了安全性,这个尝试是值得的。

其次,如果项目对效率确实有很高的要求,那么你不妨在开发阶段遵照这个原则使用clone,然后在项目优化阶段,作为一种优化手段,将不必要的Clone操作去掉。这样就能在保证安全的前提下做到最好的优化。

2、什么时候使用go的问题

有两种思维逻辑会想到使用goroutine:

1 业务逻辑需要并发

比如一个服务器,接收请求,阻塞式的方法是一个请求处理完成后,才开始第二个请求的处理。其实在设计的时候我们一定不会这么做,我们会在一开始就已经想到使用并发来处理这个场景,每个请求启动一个goroutine为它服务,这样就达到了并行的效果。这种goroutine直接按照思维的逻辑来使用goroutine

2 性能优化需要并发

一个场景是这样:需要给一批用户发送消息,正常逻辑会使用

for _, user := range users {
    sendMessage(user)
 
}
Nach dem Login kopieren

但是在考虑到性能问题的时候,我们就不会这样做,如果users的个数很大,比如有1000万个用户?我们就没必要将1000万个用户放在一个routine中运行处理,考虑将1000万用户分成1000份,每份开一个goroutine,一个goroutine分发1万个用户,这样在效率上会提升很多。这种是性能优化上对goroutine的需求

按照项目开发的流程角度来看。在项目开发阶段,第一种思路的代码实现会直接影响到后续的开发实现,因此在项目开发阶段应该马上实现。

但是第二种,项目中是由很多小角落是可以使用goroutine进行优化的,但是如果在开发阶段对每个优化策略都考虑到,那一定会直接打乱你的开发思路,会让你的开发周期延长,而且很容易埋下潜在的不安全代码。

因此第二种情况在开发阶段绝不应该直接使用goroutine,而该在项目优化阶段以优化的思路对项目进行重构。

推荐:golang开发栏目

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung der Verwendung von Goroutine in der Go-Sprache. 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ß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)

Was ist das Problem mit Warteschlangen -Thread in Go's Crawler Colly? Was ist das Problem mit Warteschlangen -Thread in Go's Crawler Colly? Apr 02, 2025 pm 02:09 PM

Das Problem der Warteschlange Threading In Go Crawler Colly untersucht das Problem der Verwendung der Colly Crawler Library in Go -Sprache. Entwickler stoßen häufig auf Probleme mit Threads und Anfordern von Warteschlangen. � ...

Welche Bibliotheken werden für die Operationen der schwimmenden Punktzahl in Go verwendet? Welche Bibliotheken werden für die Operationen der schwimmenden Punktzahl in Go verwendet? Apr 02, 2025 pm 02:06 PM

In der Bibliothek, die für den Betrieb der Schwimmpunktnummer in der GO-Sprache verwendet wird, wird die Genauigkeit sichergestellt, wie die Genauigkeit ...

Wie löste ich das Problem des Typs des user_id -Typs bei der Verwendung von Redis -Stream, um Nachrichtenwarteschlangen in GO -Sprache zu implementieren? Wie löste ich das Problem des Typs des user_id -Typs bei der Verwendung von Redis -Stream, um Nachrichtenwarteschlangen in GO -Sprache zu implementieren? Apr 02, 2025 pm 04:54 PM

Das Problem der Verwendung von RETISTREAM zur Implementierung von Nachrichtenwarteschlangen in der GO -Sprache besteht darin, die Go -Sprache und Redis zu verwenden ...

Warum hat das Drucken von Saiten mit Println und String () -Funktionen unterschiedliche Effekte? Warum hat das Drucken von Saiten mit Println und String () -Funktionen unterschiedliche Effekte? Apr 02, 2025 pm 02:03 PM

Der Unterschied zwischen Stringdruck in GO -Sprache: Der Unterschied in der Wirkung der Verwendung von Println und String () ist in Go ...

Was soll ich tun, wenn die benutzerdefinierten Strukturbezeichnungen in Goland nicht angezeigt werden? Was soll ich tun, wenn die benutzerdefinierten Strukturbezeichnungen in Goland nicht angezeigt werden? Apr 02, 2025 pm 05:09 PM

Was soll ich tun, wenn die benutzerdefinierten Strukturbezeichnungen in Goland nicht angezeigt werden? Bei der Verwendung von Goland für GO -Sprachentwicklung begegnen viele Entwickler benutzerdefinierte Struktur -Tags ...

Was ist der Unterschied zwischen 'var' und 'Typ' Typenwort Definition in der GO -Sprache? Was ist der Unterschied zwischen 'var' und 'Typ' Typenwort Definition in der GO -Sprache? Apr 02, 2025 pm 12:57 PM

Zwei Möglichkeiten, Strukturen in der GO -Sprache zu definieren: Der Unterschied zwischen VAR- und Typ -Schlüsselwörtern. Bei der Definition von Strukturen sieht die Sprache oft zwei verschiedene Schreibweisen: Erstens ...

Welche Bibliotheken in GO werden von großen Unternehmen entwickelt oder von bekannten Open-Source-Projekten bereitgestellt? Welche Bibliotheken in GO werden von großen Unternehmen entwickelt oder von bekannten Open-Source-Projekten bereitgestellt? Apr 02, 2025 pm 04:12 PM

Welche Bibliotheken in GO werden von großen Unternehmen oder bekannten Open-Source-Projekten entwickelt? Bei der Programmierung in Go begegnen Entwickler häufig auf einige häufige Bedürfnisse, ...

Warum ist es notwendig, Zeiger zu verabschieden, wenn sie GO- und Viper -Bibliotheken verwenden? Warum ist es notwendig, Zeiger zu verabschieden, wenn sie GO- und Viper -Bibliotheken verwenden? Apr 02, 2025 pm 04:00 PM

Go Zeigersyntax und Probleme bei der Verwendung der Viper -Bibliothek bei der Programmierung in Go -Sprache. Es ist entscheidend, die Syntax und Verwendung von Zeigern zu verstehen, insbesondere in ...

See all articles