Maison > développement back-end > Golang > le corps du texte

Maîtriser la concurrence avancée de Go : augmentez la puissance et les performances de votre code

Susan Sarandon
Libérer: 2024-11-19 07:41:02
original
630 Les gens l'ont consulté

Mastering Go

La concurrence est la pierre angulaire de la conception de Go, et c'est l'une des raisons pour lesquelles le langage a gagné en popularité. Bien que la plupart des développeurs soient familiers avec les goroutines et les canaux de base, il existe tout un monde de modèles avancés qui attendent d'être explorés.

Commençons par sync.Cond, une puissante primitive de synchronisation souvent négligée. C'est particulièrement utile lorsque vous devez coordonner plusieurs goroutines en fonction d'une condition. Voici un exemple simple :

var count int
var mutex sync.Mutex
var cond = sync.NewCond(&mutex)

func main() {
    for i := 0; i < 10; i++ {
        go increment()
    }

    time.Sleep(time.Second)
    cond.Broadcast()
    time.Sleep(time.Second)
    fmt.Println("Final count:", count)
}

func increment() {
    mutex.Lock()
    defer mutex.Unlock()
    cond.Wait()
    count++
}
Copier après la connexion
Copier après la connexion

Dans cet exemple, nous utilisons sync.Cond pour coordonner plusieurs goroutines. Ils attendent tous un signal avant d'incrémenter le décompte. Ce modèle est pratique lorsque vous devez synchroniser plusieurs goroutines en fonction d'une condition spécifique.

Les opérations atomiques sont un autre outil puissant de la boîte à outils de concurrence de Go. Ils permettent une synchronisation sans verrouillage, ce qui peut améliorer considérablement les performances dans certains scénarios. Voici comment vous pouvez utiliser des opérations atomiques pour implémenter un compteur simple :

var counter int64

func main() {
    for i := 0; i < 1000; i++ {
        go func() {
            atomic.AddInt64(&counter, 1)
        }()
    }
    time.Sleep(time.Second)
    fmt.Println("Counter:", atomic.LoadInt64(&counter))
}
Copier après la connexion
Copier après la connexion

Ce code est beaucoup plus simple et potentiellement plus efficace que l'utilisation d'un mutex pour une opération aussi basique.

Parlons maintenant de quelques modèles plus complexes. Le modèle fan-out/fan-in est un moyen puissant de paralléliser le travail. Voici une implémentation simple :

func fanOut(input <-chan int, workers int) []<-chan int {
    channels := make([]<-chan int, workers)
    for i := 0; i < workers; i++ {
        channels[i] = work(input)
    }
    return channels
}

func fanIn(channels ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)

    output := func(c <-chan int) {
        for n := range c {
            out <- n
        }
        wg.Done()
    }

    wg.Add(len(channels))
    for _, c := range channels {
        go output(c)
    }

    go func() {
        wg.Wait()
        close(out)
    }()

    return out
}

func work(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}
Copier après la connexion
Copier après la connexion

Ce modèle vous permet de répartir le travail sur plusieurs goroutines, puis de collecter les résultats. C'est incroyablement utile pour les tâches liées au processeur qui peuvent être parallélisées.

Les pools de travailleurs sont un autre modèle courant dans la programmation simultanée. Ils vous permettent de limiter le nombre de goroutines exécutées simultanément, ce qui peut être crucial pour gérer l'utilisation des ressources. Voici une implémentation simple :

func workerPool(jobs <-chan int, results chan<- int, workers int) {
    var wg sync.WaitGroup
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                results <- job * 2
            }
        }()
    }
    wg.Wait()
    close(results)
}
Copier après la connexion
Copier après la connexion

Ce pool de travailleurs traite les tâches simultanément, mais limite le nombre d'opérations simultanées au nombre de travailleurs.

Les pipelines sont un autre modèle puissant dans Go. Ils vous permettent de diviser les opérations complexes en étapes pouvant être traitées simultanément. Voici un exemple simple :

func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

func main() {
    for n := range sq(sq(gen(2, 3))) {
        fmt.Println(n)
    }
}
Copier après la connexion
Copier après la connexion

Ce pipeline génère des nombres, les met au carré, puis met à nouveau les résultats au carré. Chaque étape s'exécute dans sa propre goroutine, permettant un traitement simultané.

Les arrêts progressifs sont cruciaux dans les systèmes de production. Voici un modèle pour mettre en œuvre un arrêt progressif :

func main() {
    done := make(chan struct{})
    go worker(done)

    // Simulate work
    time.Sleep(time.Second)

    // Signal shutdown
    close(done)
    fmt.Println("Shutting down...")
    time.Sleep(time.Second) // Give worker time to clean up
}

func worker(done <-chan struct{}) {
    for {
        select {
        case <-done:
            fmt.Println("Worker: Cleaning up...")
            return
        default:
            fmt.Println("Worker: Working...")
            time.Sleep(100 * time.Millisecond)
        }
    }
}
Copier après la connexion
Copier après la connexion

Ce modèle permet au travailleur de nettoyer et de sortir gracieusement lorsqu'il est signalé.

La gestion des délais d'attente est un autre aspect crucial de la programmation simultanée. L'instruction select de Go rend cela facile :

func doWork() <-chan int {
    ch := make(chan int)
    go func() {
        time.Sleep(2 * time.Second)
        ch <- 42
    }()
    return ch
}

func main() {
    select {
    case result := <-doWork():
        fmt.Println("Result:", result)
    case <-time.After(1 * time.Second):
        fmt.Println("Timeout!")
    }
}
Copier après la connexion

Ce code expirera si doWork prend plus d'une seconde pour produire un résultat.

La propagation d'annulation est un modèle dans lequel un signal d'annulation est transmis à travers une chaîne d'appels de fonction. Le package de contexte dans Go est conçu pour cela :

var count int
var mutex sync.Mutex
var cond = sync.NewCond(&mutex)

func main() {
    for i := 0; i < 10; i++ {
        go increment()
    }

    time.Sleep(time.Second)
    cond.Broadcast()
    time.Sleep(time.Second)
    fmt.Println("Final count:", count)
}

func increment() {
    mutex.Lock()
    defer mutex.Unlock()
    cond.Wait()
    count++
}
Copier après la connexion
Copier après la connexion

Ce modèle permet d'annuler facilement les opérations de longue durée.

Maintenant, regardons quelques exemples concrets. Voici une implémentation simple d'un équilibreur de charge :

var counter int64

func main() {
    for i := 0; i < 1000; i++ {
        go func() {
            atomic.AddInt64(&counter, 1)
        }()
    }
    time.Sleep(time.Second)
    fmt.Println("Counter:", atomic.LoadInt64(&counter))
}
Copier après la connexion
Copier après la connexion

Cet équilibreur de charge distribue les requêtes au serveur le moins chargé, mettant à jour la charge en temps réel.

La limitation de débit est une autre exigence courante dans les systèmes distribués. Voici une implémentation simple d'un bucket de jetons :

func fanOut(input <-chan int, workers int) []<-chan int {
    channels := make([]<-chan int, workers)
    for i := 0; i < workers; i++ {
        channels[i] = work(input)
    }
    return channels
}

func fanIn(channels ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)

    output := func(c <-chan int) {
        for n := range c {
            out <- n
        }
        wg.Done()
    }

    wg.Add(len(channels))
    for _, c := range channels {
        go output(c)
    }

    go func() {
        wg.Wait()
        close(out)
    }()

    return out
}

func work(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}
Copier après la connexion
Copier après la connexion

Ce limiteur de débit autorise un certain nombre d'opérations par seconde, lissant les rafales de trafic.

Les files d'attente de tâches distribuées sont un cas d'utilisation courant des fonctionnalités de concurrence de Go. Voici une implémentation simple :

func workerPool(jobs <-chan int, results chan<- int, workers int) {
    var wg sync.WaitGroup
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                results <- job * 2
            }
        }()
    }
    wg.Wait()
    close(results)
}
Copier après la connexion
Copier après la connexion

Cette file d'attente de tâches distribuée permet à plusieurs travailleurs de traiter des tâches simultanément.

Le runtime de Go fournit des outils puissants pour gérer les goroutines. La fonction GOMAXPROCS vous permet de contrôler le nombre de threads du système d'exploitation pouvant exécuter du code Go simultanément :

func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

func main() {
    for n := range sq(sq(gen(2, 3))) {
        fmt.Println(n)
    }
}
Copier après la connexion
Copier après la connexion

Cela définit le nombre de threads du système d'exploitation sur le nombre de processeurs, ce qui peut améliorer les performances des tâches liées au processeur.

L'optimisation du code simultané implique souvent un équilibre entre le parallélisme et les frais généraux liés à la création et à la gestion des goroutines. Les outils de profilage comme pprof peuvent aider à identifier les goulots d'étranglement :

func main() {
    done := make(chan struct{})
    go worker(done)

    // Simulate work
    time.Sleep(time.Second)

    // Signal shutdown
    close(done)
    fmt.Println("Shutting down...")
    time.Sleep(time.Second) // Give worker time to clean up
}

func worker(done <-chan struct{}) {
    for {
        select {
        case <-done:
            fmt.Println("Worker: Cleaning up...")
            return
        default:
            fmt.Println("Worker: Working...")
            time.Sleep(100 * time.Millisecond)
        }
    }
}
Copier après la connexion
Copier après la connexion

Ce code active pprof, vous permettant de profiler votre code concurrent et d'identifier les problèmes de performances.

En conclusion, les fonctionnalités de concurrence de Go fournissent une boîte à outils puissante pour créer des systèmes efficaces et évolutifs. En maîtrisant ces modèles et techniques avancés, vous pouvez tirer pleinement parti des processeurs multicœurs modernes et créer des applications robustes et hautes performances. N'oubliez pas que la concurrence n'est pas seulement une question de vitesse : il s'agit également de concevoir un code propre et gérable, capable de gérer des scénarios complexes et réels. Alors allez-y et relevez ces défis simultanés !


Nos créations

N'oubliez pas de consulter nos créations :

Centre des investisseurs | Vie intelligente | Époques & Échos | Mystères déroutants | Hindutva | Développeur Élite | Écoles JS


Nous sommes sur Medium

Tech Koala Insights | Epoques & Echos Monde | Support Central des Investisseurs | Mystères déroutants Medium | Sciences & Epoques Medium | Hindutva moderne

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal