Maison >développement back-end >Golang >Chaîne Golang simplifiée à l'extrême !
L'article explique les canaux Go, qui permettent une communication sécurisée entre les goroutines. Il explique comment créer, envoyer et recevoir des données via des canaux, en distinguant les types sans tampon et avec tampon. Il souligne l’importance de fermer les canaux pour éviter les blocages et améliorer la gestion des ressources. Enfin, il présente l'instruction select pour gérer efficacement les opérations sur plusieurs canaux.
Go, ou Golang, est un langage de programmation puissant conçu pour la simplicité et l'efficacité. L'une de ses caractéristiques les plus marquantes est le concept de canaux, qui facilitent la communication entre les goroutines. Les canaux permettent un échange et une synchronisation de données en toute sécurité, rendant la programmation simultanée plus facile et plus gérable.
Dans cet article, nous explorerons les canaux dans Go, en décomposant leur création, leur transmission de données et leur réception. Cela vous aidera à comprendre comment exploiter efficacement les canaux dans vos applications.
Pour créer une chaîne dans Go, vous utilisez la fonction make. Voici un extrait de code simple montrant comment créer une chaîne :
package main import "fmt" func main() { // Create a channel of type int ch := make(chan int) fmt.Println("Channel created:", ch) }
Dans cet exemple, nous créons un canal ch qui peut envoyer et recevoir des entiers. Le canal n'est pas tamponné par défaut, ce qui signifie qu'il sera bloqué jusqu'à ce que l'expéditeur et le destinataire soient prêts.
Lorsque vous exécutez le code Go fourni, le résultat ressemblera à ceci :
Channel created: 0xc000102060
Création de chaîne :
Adresse de la chaîne :
Une fois qu'un canal est créé, vous pouvez y envoyer des données à l'aide de l'opérateur <-. Voici comment envoyer des données à la chaîne :
go func() { ch <- 42 // Sending the value 42 to the channel }()
Dans cet extrait, nous démarrons une nouvelle goroutine qui envoie la valeur entière 42 dans le canal ch. Cette opération asynchrone permet au programme principal de continuer à s'exécuter pendant que la valeur est envoyée.
Pour recevoir des données d'un canal, vous utilisez également l'opérateur <-. Voici comment lire sur la chaîne :
value := <-ch // Receiving data from the channel fmt.Println("Received value:", value)
Dans cet exemple, nous lisons à partir du canal ch et stockons la valeur reçue dans la valeur variable. Le programme se bloquera sur cette ligne jusqu'à ce qu'une valeur soit disponible en lecture.
Dans Go, les canaux peuvent être classés principalement en deux types : les canaux sans tampon et les canaux avec tampon. Comprendre ces types est essentiel pour une programmation simultanée efficace.
Un canal sans tampon est le type le plus simple. Il n'a aucune capacité de stockage de données ; cela nécessite que l'expéditeur et le destinataire soient prêts en même temps.
ch := make(chan int) // Unbuffered channel go func() { ch <- 1 // Sends data; blocks until received }() value := <-ch // Receives data; blocks until sent fmt.Println("Received:", value)
Les canaux tamponnés vous permettent de spécifier une capacité, ce qui signifie qu'ils peuvent contenir un nombre limité de valeurs avant de bloquer les envois.
ch := make(chan int, 2) // Buffered channel with capacity of 2 ch <- 1 // Does not block ch <- 2 // Does not block // ch <- 3 // Would block since the buffer is full fmt.Println("Values sent to buffered channel.")
In Go, closing a channel is an operation that signals that no more values will be sent on that channel. This is done using the close(channel) function. Once a channel is closed, it cannot be reopened or sent to again.
Signal Completion: Closing a channel indicates to the receiving goroutine that no more values will be sent. This allows the receiver to know when to stop waiting for new messages.
Preventing Deadlocks: If a goroutine is reading from a channel that is never closed, it can lead to deadlocks where the program hangs indefinitely, waiting for more data that will never arrive.
Resource Management: Closing channels helps in managing resources effectively, as it allows the garbage collector to reclaim memory associated with the channel once it is no longer in use.
Iteration Control: When using a for range loop to read from a channel, closing the channel provides a clean way to exit the loop once all messages have been processed.
In this section, we will explore a Go code snippet that demonstrates the use of unbuffered channels. We will analyze the behavior of the code with and without closing the channel, as well as the implications of each approach.
Here’s the original code snippet without the close statement:
package main import ( "fmt" ) func main() { messages := make(chan string) go func() { messages <- "Message 1" messages <- "Message 2" messages <- "Message 3" // close(messages) // This line is removed }() for msg := range messages { fmt.Println(msg) } }
fatal error: all goroutines are asleep - deadlock!
When you run this code, it will compile and execute, but it will hang indefinitely without producing the expected output. The reason is that the for msg := range messages loop continues to wait for more messages, and since the channel is never closed, the loop has no way of knowing when to terminate. This results in a deadlock situation, causing the program to hang.
Now, let’s add the close statement back into the code:
package main import ( "fmt" ) func main() { messages := make(chan string) go func() { messages <- "Message 1" messages <- "Message 2" messages <- "Message 3" close(messages) // Close the channel when done }() for msg := range messages { fmt.Println(msg) } }
With the close statement included, the output of this code will be:
Message 1 Message 2 Message 3
In this version of the code:
Let's imagine a scenario where channels in Go are like people in a conversation.
Scene: A Coffee Shop
Characters:
Conversation:
Alice: "Hey Bob, did you hear about the new project? We need to brainstorm!"
Bob sips his coffee, staring blankly. The conversation is paused.
Alice: "Hello? Are you there?"
Bob looks up, still processing.
Bob: "Oh, sorry! I was... uh... thinking."
Minutes pass. Alice starts to wonder if Bob is even still in the chat.
Alice: "Should I keep talking or just wait for a signal?"
Bob finally responds, but it’s completely off-topic.
Bob: "Did you know that sloths can hold their breath longer than dolphins?"
Alice facepalms.
Alice: "Great, but what about the project?"
Bob shrugs, lost in thought again. The coffee shop becomes awkwardly silent.
Alice: "Is this conversation ever going to close, or will I just be here forever?"
Bob, now fascinated by the barista, mutters something about coffee beans.
Alice: "This is like a Go channel that never gets closed! I feel like I’m stuck in an infinite loop!"
Bob finally looks back, grinning.
Bob: "So... about those sloths?"
Moral of the Story: Sometimes, when channels (or conversations) don’t close, you end up with endless topics and no resolution—just like a chat that drags on forever without a conclusion!
Go's concurrency model is built around goroutines and channels, which facilitate communication between concurrent processes. The select statement is vital for managing multiple channel operations effectively.
Here's an example of using select with channels:
package main import ( "fmt" "time" ) func main() { ch1 := make(chan string) ch2 := make(chan string) go func() { time.Sleep(1 * time.Second) ch1 <- "Result from channel 1" }() go func() { time.Sleep(2 * time.Second) ch2 <- "Result from channel 2" }() select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) } }
Result from channel 1
In Go, the select statement is a powerful construct used for handling multiple channel operations. When working with channels, you might wonder why a program prints only one output when multiple channels are involved. Let’s explore this concept through a simple example.
Consider the program that involves two channels: ch1 and ch2. Each channel receives a message after a delay, but only one message is printed at the end. You might ask, "Why does it only print one output?"
Channel Initialization: Both ch1 and ch2 are created to handle string messages.
Goroutines:
Select Statement: The select statement listens for messages from both channels. It blocks until one of the channels is ready to send a message.
Q: Is it possible to wait for all channels in select to print all outputs?
A: No, the select statement is designed to handle one case at a time. To wait for multiple channels and print all outputs, you would need to use a loop or wait group.
Q: What happens if both channels are ready at the same time?
A: If both channels are ready simultaneously, Go will choose one at random to process, so the output may vary between executions.
Q: Can I handle timeouts with select?
A: Yes, you can include a timeout case in the select statement, allowing you to specify a duration to wait for a message.
Q: How can I ensure I receive messages from both channels?
A: To receive messages from both channels, consider using a loop with a select statement inside it, or use a sync.WaitGroup to wait for multiple goroutines to complete their tasks.
To ensure you receive messages from both channels in Go, you can use a sync.WaitGroup. This allows you to wait for multiple goroutines to complete before proceeding.
Here’s an example:
package main import ( "fmt" "sync" "time" ) func main() { ch1 := make(chan string) ch2 := make(chan string) var wg sync.WaitGroup // Start goroutine for channel 1 wg.Add(1) go func() { defer wg.Done() time.Sleep(1 * time.Second) ch1 <- "Result from channel 1" }() // Start goroutine for channel 2 wg.Add(1) go func() { defer wg.Done() time.Sleep(2 * time.Second) ch2 <- "Result from channel 2" }() // Wait for both goroutines to finish go func() { wg.Wait() close(ch1) close(ch2) }() // Collect results from both channels results := []string{} for i := 0; i < 2; i++ { select { case msg1 := <-ch1: results = append(results, msg1) case msg2 := <-ch2: results = append(results, msg2) } } // Print all results for _, result := range results { fmt.Println(result) } }
Result from channel 1 Result from channel 2
Channels and WaitGroup: Two channels, ch1 and ch2, are created. A sync.WaitGroup is used to wait for both goroutines to finish.
Goroutines: Each goroutine sends a message to its channel after a delay. The wg.Done() is called to signal completion.
Closing Channels: After all goroutines are done, the channels are closed to prevent any further sends.
Collecting Results: A loop with a select statement is used to receive messages from both channels until both messages are collected.
Final Output: The collected messages are printed.
This method ensures that you wait for both channels to send their messages before proceeding.
If you're interested in learning more about using sync.WaitGroup in Go, check out this article on concurrency: Golang Concurrency: A Fun and Fast Ride.
Let's compare the two versions of a program in terms of their structure, execution, and timing.
This version processes the jobs sequentially, one after the other.
package main import ( "fmt" "time" ) func worker(id int, job int) string { time.Sleep(time.Second) // Simulate work return fmt.Sprintf("Worker %d completed job %d", id, job) } func main() { start := time.Now() results := make([]string, 5) for j := 1; j <= 5; j++ { results[j-1] = worker(1, j) // Call the worker function directly } for _, result := range results { fmt.Println(result) } duration := time.Since(start) fmt.Printf("It took %s to execute!", duration) }
Output:
Worker 1 completed job 1 Worker 1 completed job 2 Worker 1 completed job 3 Worker 1 completed job 4 Worker 1 completed job 5 It took 5.048703s to execute!
This version processes the jobs concurrently using goroutines and channels.
package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- string) { for job := range jobs { time.Sleep(time.Second) // Simulate work results <- fmt.Sprintf("Worker %d completed job %d", id, job) } } func main() { start := time.Now() jobs := make(chan int, 5) results := make(chan string) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= 5; j++ { jobs <- j } close(jobs) for a := 1; a <= 5; a++ { fmt.Println(<-results) } duration := time.Since(start) fmt.Printf("It took %s to execute!", duration) }
Output:
Worker 1 completed job 1 Worker 2 completed job 2 Worker 3 completed job 3 Worker 1 completed job 4 Worker 2 completed job 5 It took 2.0227664s to execute!
Structure:
Exécution :
Calendrier :
La version simultanée est nettement plus rapide car elle exploite l'exécution parallèle, permettant de traiter plusieurs tâches simultanément. Cela réduit le temps d'exécution total à environ le temps nécessaire pour terminer le travail le plus long, divisé par le nombre de travailleurs, plutôt que de résumer le temps pour chaque travail comme dans la version séquentielle.
Documentation Go - Goroutines
Goroutines
Documentation Go - Chaînes
Chaînes
Go Blog - Concurrence dans Go
Concurrence dans Go
Documentation Go - La déclaration select
Sélectionnez la déclaration
Go Tour - Chaînes
Tour de Go : Chaînes
En résumé, l'article donne un aperçu clair et simplifié des canaux dans Go, soulignant leur rôle dans la facilitation d'une communication sécurisée entre goroutines. En expliquant les concepts de canaux non tamponnés et tamponnés, l'article met en évidence leurs comportements distincts et les cas d'utilisation appropriés. De plus, cela souligne l’importance de fermer les canaux pour éviter les blocages et garantir une gestion efficace des ressources. Avec des exemples de code pratiques et des analogies pertinentes, l'article donne aux lecteurs une compréhension fondamentale de la façon d'utiliser efficacement les canaux dans leurs applications Go, ouvrant la voie à une programmation simultanée plus robuste.
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!