Olivier Lemer
Comparaison avec Java
Ressources et Développement
Bases du langage
Héritage et Composition
Concurrence
Programmation TCP-IP
1.
2.
3.
4.
5.
6.
Histoire
2007 - Créé en interne pour Google, par Robert Gresemer, Rob Pike et Ken Thompson.
Pensé pour
Logiciels serveurs
2009 - Devient Open Source avec communauté très active.
2012 - Publication de la version 1.0, stable.
Scalability (10^6 machines)
(Concurrence builtin au langage)
Similarités
instanceof
)Différences
Philosophie de Go : lisibilité et de simplicité, afin d'éviter les erreurs d'implémentations.
Hello, World!
public class Main {
public static void main(String[] args) {
System.out.println("Hello, world!"); }
}
main.java
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
hello.go
public class Main {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
Point virgule optionnel
Sur le site officiel : golang.org
Structure d'un projet
.
├── server
│ ├── server.go
│ └── db.go
├── main.go
├── util.go
└── go.mod
Go module
go.mod
Go packages
package <name>
pour indiquer son package.Main package
Le package à la racine du module est spécial et doit contenir une fonction main()
.
Export entre packages
Variable/Fonction avec Majuscule
: identifiant exporté.
Variable/Fonction avec minuscule
: identifiant pas exporté.
Structure d'un projet
Notez : pas besoin d'importer util.go
dans hello.go
, car ils sont dans le même package.
package main
import "fmt"
func main() {
fmt.Println(reverse("Hello, World!"))
}
/hello.go
package main
func reverse(s string) string {
b := []rune(s)
for i := 0; i < len(b)/2; i++ {
j := len(b) - i - 1
b[i], b[j] = b[j], b[i]
}
return string(b)
}
/util.go
Structure d'un projet
package main
import (
"fmt"
"module_name/util"
)
func main() {
fmt.Println(util.Reverse("Hello, World!"))
}
/hello.go
package util
func Reverse(s string) string {
b := []rune(s)
for i := 0; i < len(b)/2; i++ {
j := len(b) - i - 1
b[i], b[j] = b[j], b[i]
}
return string(b)
}
/util/util.go
Même exemple avec plusieurs packages.
Reverse
avec majuscule => exporté
Package util
importé
Structure conventionnelle d'un projet
.
├── cmd
│ ├── server
│ │ └── main.go
│ └── client
│ └── main.go
├── internal
│ ├── server
│ │ └── server.go
│ ├── client
│ │ ├── cmd_parser.go
│ │ └── client.go
│ └── mutex
│ └── mutex.go
└── go.mod
Packages cmd
Contient uniquement les sources d'exécutables, qui référencent la logique interne.
Généralement très légers.
Packages internes
Modularisent la logique interne.
Logique du serveur.
Logique du client.
Logique commune.
Import de packages externes
go get [url]
Obtient le package pointé par l'url, et crée une dépendance à celui-ci dans go.mod
.
go get github.com/example
Le package peut ensuite être importé et utilisé dans tout fichier du module.
import "github.com/example"
Nous ne le ferons pas dans les labos de ce cours.
go build [package]
Compile un package (ou un fichier formant un package) en un executable dans le dossier courant.
Compiler et installer
go build hello.go
go install [package]
Compile un package (ou un fichier formant un package), puis installe l'executable dans $GOBIN
, donc $HOME/go/bin/
par défaut.
go install
go run [package]
Compile et exécute un package (ou un fichier formant un package) d'éxécutable.
go run hello.go
Tester
go test
Compile et execute toute fonction Test*
dans tout fichier *_test.go
.
go test
pakage util
import "testing"
func TestSomething(t *testing.T) {
// ...
}
util/util_test.go
Collections
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128
Types de base
Variables
var <name> <type> = <value>
Type optionel s'il peut être inféré de la valeur assignée.
Valeur peut être omise : valeur "zéro" alors utilisée.
<name> := <value>
Forme équivalente succinte (uniquement pour variables locales muables)
Symbole ':='
utilisé à la place de '='
.
Type omis car inféré de la valeur assignée.
Peut être const
pour créer une constante.
Tableaux et slices
Tableau [5]byte
Slice du tableau
Pointeur sur début
Taille
Capacité
'o'
'l'
'l'
'e'
'h'
[n]type // Type d'un array de taille n et de type type
Syntaxe du type d'un tableau
[2]string{"Hello", "there"}
Syntaxe d'un littéral tableau
tableau[a:b] // slice entre a (incl) et b (excl) de tableau
Syntaxe d'un slice
2
4
tableau := [5]byte{'h','e','l','l','o'}
slice := tableau[1:3] // ['e', 'l']
slice = append(slice, 'i') // ['e', 'l', 'i']
Augmente la taille et insère la valeur.
Si le tableau est trop petit, en crée un nouveau et pointe sur celui-là.
Tableaux et slices
Tableau [5]byte
Slice du tableau
Pointeur sur début
Taille
Capacité
'o'
'i'
'l'
'e'
'h'
tableau := [5]byte{'h','e','l','l','o'}
slice := tableau[1:3] // ['e', 'l']
slice = append(slice, 'i') // ['e', 'l', 'i']
[n]type // Type d'un array de taille n et de type type
Syntaxe du type d'un tableau
[2]string{"Hello", "there"}
Syntaxe d'un littéral tableau
tableau[a:b] // slice entre a (incl) et b (excl) de tableau
Syntaxe d'un slice
3
4
Note : modifier la slice modifie le tableau sous-jacent.
Augmente la taille et insère la valeur.
Si le tableau est trop petit, en crée un nouveau et pointe sur celui-là.
Tableaux et slices - Question
tableau := [5]byte{'h','e','l','l','o'}
slice := tableau[0:3] // ['h', 'e', 'l']
slice[0] = 'R'
slice = append(slice, 'i')
slice = append(slice, 'u')
slice = append(slice, 'm')
slice[0] = 'H'
Que contient tableau
?
Initialement, la slice pointe sur le tableau. Tout changement sur la slice s'applique donc au tableau, y inclus les append.
Lorsqu'on tente d'append au delà de la capacité du tableau ('m')
, un nouveau tableau est créé et la slice pointe dessus.
À partir de ce moment, tout changement se fait sur ce nouveau tableau, 'R'
n'est donc pas remplacé par 'H'
ligne 11.
Tableaux et slices
[n]type // Type d'un array de taille n et de type type
Syntaxe du type d'un tableau
[2]string{"Hello", "there"}
Syntaxe d'un littéral tableau
tableau[a:b] // slice entre a (incl) et b (excl) de tableau
Syntaxe d'un slice
make([]type, len, cap) // slice de type type, longeur len et capacité cap
Création d'un slice avec make
Tableau [5]byte
Slice du tableau
Pointeur sur début
Taille
Capacité
\0
3
5
slice := make([]byte, 3, 5)
\0
\0
\0
\0
Crée un tableau de zéros de taille 5,
et crée un slice de capacité 3 dessus.
Maps
make(map[int]string) // map vide de int vers string
Création d'une map avec make
Syntaxe d'un littéral map
map[int]string{1: "un", 2: "deux"}
map[int]string // Type d'une map de clés int et valeurs string
Syntaxe du type d'une map
nombres := make(map[int]string)
nombres[1] = "un" // Ajout d'une valeur
nombres[1] = "one" // Remplacement d'une valeur
un := nombres[1] // Lecture d'une valeur
delete(nombres, 1) // Suppression d'une valeur
un, ok := nombres[1] // Lecture et vérification d'une valeur
Modification d'une map
true
ssi la valeur existe dans la map.
Sets
noms := make(map[string]bool)
mots := make(map[string]struct{})
Map largement suffisant : la valeur peut être une structure vide.
N'existe pas.
Conditions et boucles
Instructions conditionnelles
if <init>; <cond> {
// ...
} else if {
// ...
} else {
// ...
}
Statement court (optionnel)
Comme pour for
, permet d'initialiser une variable qui existera dans le corps du if
et du else
.
Pas de parenthèses nécessaires pour la condition.
Blocs else
et else if
optionnels
Instructions conditionnelles
switch <init>; <value> {
case <value> :
// ...
fallthrough
case <value> :
// ...
default :
// ...
}
break
par défaut.
Pour forcer l'évaluation du case
suivant, utiliser fallthrough
Bloc default
optionnel.
Switch sur une valeur
switch {
case <cond> :
// ...
case <cond> :
// ...
default :
// ...
}
Si la valeur est omise, alors les case
prennent des conditions.
On obtient alors un équivalent exact de if-elseif-elseif-else
Switch sur rien
Statement court (optionnel)
Comme pour le if
.
Boucles
for <init>; <condition>; <post> {
// ...
}
<init>
et <post>
sont optionnels
for ; <condition>; {
for <condition> {
Lorsque les deux sont omis, les ';'
peuvent l'être aussi.
Boucle for
for i, v := range <iterable> {
// ...
}
<condition>
est optionnel.
for { // infinite loop
Boucle foreach
Mot-clé range
fait partie de la syntaxe.
Peut être un array ou une map
Donne l'index (ou la clé) et la valeur. Utiliser le symbole '_'
pour ne pas récupérer dans une variable.
Boucles
Boucle while
N'existe pas.
for <condition> {
Boucle for
complètement équivalente
Fonctions
Fonctions
func compare[T comparable](a T, b T) bool {
<T> <constraints>
Paramètres de type
Paramètres
Type de retour
(Fonctions génériques)
<T
>
le nom du paramètre<constraints>
décrit des contraintes sur le type<name> <type>
func add(x, y int) int {
void
)func origin() (int, int) {
func origin() (x int, y int) {
x = 1;
y = 2;
return
}
Elles seront alors des variables dans la fonction, et seront retournées automatiquement.
func origin() (x, y int) {
Fonctions - exemples
func add(x, y int) int { // type de x omis car identique à y
return x + y
}
func swap(x, y string) (string, string) { // Plusieurs valeurs retournées
return y, x
}
func split(sum int) (x, y int) { // Valeurs retournées nommées.
x = sum * 4 / 9 // Variables x et y déjà existentes.
y = sum - x
return // Retourne x et y automatiquement.
}
func swap(x, y string) (string, string) { // Plusieurs valeurs retournées
return y, x
}
a, b := swap("Hello", "world!"); // Attribution à deux variables
_, _ := swap("Hello", "world!"); // Possibilité d'ignorer. Nombre doit correspondre.
Fonctions comme first class citizens
func(<parameter_types>) <return_types>
Syntaxe du type d'une fonction
func(<parameters>) <return_types> { <body> }
Syntaxe d'une fonction anonyme
func square(n int) int {return n * n}
func main() {
var f func(int) int // Variable de type func(int) int
f = func(n int) int { return -n } // Attribution avec fonction anonyme
f = square // Attribution avec fonction existente
}
func applyTwice(f func(int) int, n int) int { // Fonction comme argument
return f(f(n))
}
Exemple
Fonctions deviennent closures
Définition : objet représentant une fonction et l'environnement dans lequel elle a été créée.
func getUidGenerator() func() int {
var nextUid int = 0
return func() int {
nextUid += 1
return nextUid
}
}
func main() {
genUid := getUidGenerator()
id1 := genUid() // 1
id2 := genUid() // 2
}
genUid
function
nextUid += 1
return nextUid;
Environment
nextUid: 0
nextUid
utilisée dans la fonction, elle est capturée par la closure.
nextUid: 1
Fonctions deviennent closures
Définition : objet représentant une fonction et l'environnement dans lequel elle a été créée.
func getUidGenerator() func() int {
var nextUid int = 0
f := func() int {
nextUid += 1
return nextUid
}
nextUid = 42
return f
}
func main() {
genUid := getUidGenerator()
id1 := genUid() // 43
id2 := genUid() // 44
}
genUid
function
nextUid += 1
return nextUid;
Environment
nextUid: 42
Si une variable capturée change, elle change dans la closure aussi.
Structures et composition
Déclaration de types
type intGenerator func() int
type image [][]uint32
type celsius float64
type farenheit float64
var ebulitionC celsius = 100
var ebuilitonF farenheit = ebulitionC // ERROR : Cannot assign celsius to farenheit
Exemples
type <new_type> <existing_type>
Déclarer un nouveau type
type <type_alias> = <existing_type>
Déclarer un alias pour un type donné
type celsius = float64
type farenheit = float64
var ebulitionC celsius = 100
var ebuilitonF farenheit = ebulitionC // Correct ; même type : float64
Exemples
Structures
struct {
<name1> <type1>
<name2> <type2>
// ...
}
Définition : une structure est simplement un ensemble de champs nommés.
Syntaxe du type d'une structure
type Person struct {
name string
age int
}
var fred Person
Généralement associé à un nouveau type immédiatement
var fred struct {
name string
age int
};
Mais reste un type comme un autre, peut être utilisé comme tel
Structures
struct {
<name1> <type1>
<name2> <type2>
// ...
}
Définition : une structure est simplement un ensemble de champs nommés.
Syntaxe du type d'une structure
Note: les règles d'export s'appliquent aux champs : pour qu'un champ soit public en dehors du package, il doit commencer par une majuscule.
// Création d'une struct avec champs "zéros"
var fred Person // {"", 0}
// Création avec valeurs initiales fournies
var fred = Person{"fred", 99} // {"fred", 99}
// Création avec valeurs initiales fournies et nommées, ordre arbitraire
var fred = Person{age: 99, name: "fred"} // {"fred", 99}
// Création avec valeurs initiales partiellement fournies, "zéros" ailleurs
var fred = Person{name: "fred"} // {"fred", 0}
Syntaxe de création d'une valeur de type struct
Méthodes
func (<name> <type>) <name> (<parametres>) <return_types> { // ...
Syntaxe d'une méthode : déclaration du récepteur de la fonction avant son nom
func (p Person) Majeur() bool {
return p.age >= 18
}
if fred.Majeur() { // ...
Exemple
Peut donc être pensé comme du syntactic sugar pour
Note : le récepteur n'est qu'un paramètre comme un autre.
func Majeur(p Person) bool {
return p.age >= 18
}
if Majeur(fred) { // ...
Méthodes
func (<name> <type>) <name> (<parametres>) <return_types> { // ...
Syntaxe d'une méthode : déclaration du récepteur de la fonction avant son nom
<type>
ne peut pas être un type builtin
func (s string) MyLength() int { // ERROR : string ne peut pas être récepteur
En revanche, <type>
peut être un type équivalent à un type builtin
type MyString string
func (s MyString) MyLength() int { // Valide (!)
Interfaces
interface {
<method1>
<method2>
// ...
}
Syntaxe du type d'une interface
Définition : représente la catégorie des types qui implémentent des méthodes données.
type Walker interface {
StepsPerSecond() int
}
type Person struct {
// ...
}
func (p Person) StepsPerSecond() int { // Person est désormais un Walker
// ...
}
Important
Les interfaces sont satisfaites implicitement, et non explicitement comme en Java.
Si une struct implémente les méthodes d'une interface, alors elle satisfait cette interface.
Interfaces
type Walker interface {
StepsPerSecond() int
}
type Person struct { // ...
func (p Person) StepsPerSecond() int { // Person est désormais un Walker
// ...
}
func main() {
var p Person
var w Walker = p // Polymorphisme
}
Polymorphisme
var v <interface> = <valeur> // si <valeur> satisfait <interface>
Exemple
Composition vs. héritage
class Runner {
// ...
public void run(Task task) {
task.run();
}
public void runAll(Task[] tasks) {
for (Task task : tasks) {
run(task);
}
}
}
CountingRunner runner = new CountingRunner();
Task[] tasks = // ...
runner.runAll(tasks);
Comptera chaque tache deux fois, à cause de dynamic dispatch.
class CountingRunner extends Runner {
private int count;
// ...
@Override public void run(Task task) {
count++;
super.run(task);
}
@Override public void runAll(Task[] task) {
count += tasks.length;
super.runAll(tasks);
}
}
Dépend de l'implémentation de Runner
: si runAll
appelait task.run()
, pas de problème.
Risques de bugs étranges, dus au fort couplage.
: héritage
Composition vs. héritage
type Runner struct{}
func (r Runner) Run(t Task) {
t.Run()
}
func (r Runner) RunAll(ts []Task) {
for _, t := range ts {
r.Run(t)
}
}
CountingRunner runner = new CountingRunner();
Task[] tasks = // ...
runner.runAll(tasks);
type CountingRunner struct {
runner Runner
count int
}
func (r CountingRunner) Run(t Task) {
r.count++
r.runner.Run(t)
}
func (r CountingRunner) RunAll(ts []Task) {
r.count += len(ts)
r.runner.RunAll(ts)
}
: composition
Comptera chaque tache une seule fois : plus de dynamic dispatch.
Couplage faible, simplicité accrue, moins de bugs.
Composition vs. héritage : héritage
Runner
run(Task t)
runall(Task[] ts)
CountingRunner
run(Task t)
runall(Task[] ts)
hérite de
appelle
appelle
Dynamic Dispatch
Composition vs. héritage : composition
Runner
run(Task t)
runall(Task[] ts)
CountingRunner
run(Task t)
runall(Task[] ts)
est composé de
appelle
appelle
Runner
est indépendant de CountingRunner
Runner
peut être créé après CountingRunner
.Runner
peut être remplacé facilement par un mock pour les tests.Mais, on doit réécrire ce qu'on aimerait simplement hériter.
Or do we...
Struct embedding
type Employee struct {
Person
employeeId int
}
Si un champ d'une struct n'est qu'un type, on dit qu'il est incorporé à cette struct.
var e Employee
e.Person.Name // Champ de Person
var e Employee
e.Name // Champ de Person
e.GetName() // Méthode de Person
Struct embedding
type Runner struct {
name string
}
func (r Runner) GetName() string { return r.name }
// ...
type CountingRunner struct {
runner Runner
count int
}
// Obligé de réécrire GetName qui ne fait qu'appeler son parent
func (r CountingRunner) GetName() string { return r.runner.Name() }
// ...
Exemple - sans struct embedding
Struct embedding
type Runner struct {
name string
}
func (r Runner) GetName() string { return r.name }
// ...
type CountingRunner struct {
Runner
count int
}
// Plus nécessaire : automatiquement défini sur CountingRunner
// func (r CountingRunner) GetName() string { return r.runner.Name() }
// ...
Exemple - sans struct embedding
Struct embedding
type Person struct {
Name string
}
func (p Person) Introduce() {
fmt.Println("Hi, I'm ", p.Name)
}
type Employee struct {
Person
EmployeeId int
}
func (e Employee) GetEmail() string {
return e.Name + "@entreprise.ch"
}
func main() {
e := Employee{Person: Person{"Peter"}, EmployeeId: 123}
e.Introduce() // Fonctionne, et affiche "Hi, I'm Peter"
}
Autre exemple
Conversion de types
Conversion de types
<new_type>(<value>)
Syntaxe d'une conversion de type (cast) statique
var x float64 = 3.6
var n int = 2
n = int(x) // Avec troncation => 2
x = float64(n) // => 2.0
Exemples
s := "aéb" // string, utf8 par défaut, 32 bits car 'é' prend 2 bytes
b := []byte(s) // Conversion vers slice de bytes : longueur 4
s2 := string(b) // Conversion vers string à nouveau
r := []rune(s) // Conversion vers slice d'unicode : longueur 3
Conversion de types
<value>.(<type>)
Syntaxe d'une conversion de type (cast) dynamique, ou "interface conversion".
var w Walker = Person{}
var p = w.(Person) // Panic si w pas Person
Version risquée
var w Walker = Person{}
var p, ok = w.(Person)
if ok {
// Conversion réussie
} else {
// Conversion échouée
}
Version safe
Switch sur type
switch w.(type) {
case Person:
// ...
case Walker:
// ...
}
Syntax de conversion dynamique réutilisée pour switch sur type
Pointeurs
Pointeurs
*<type> // Pointeur sur une valeur de type <type>
Syntaxe du type d'un pointeur
new(<type>) // Allocation de mémoire de type <type> et création d'un pointeur dessus.
Création d'un pointeur sur variable anonyme
&<variable> // Pointeur sur <variable>
Obtenir un pointeur sur une valeur
*<pointeur> // Valeur pointée par <pointeur>
Déréférencer un pointeur
p := new(int) // Pointeur sur int, type *int
fmt.Println(*p) // Déréférencement du pointeur
func newIntPointer() *int {
var dummy int
return &dummy
}
p := newIntPointer() // Pointeur sur int, type *int
// Pas de dangling pointer : Go sait qu'il doit garder dummy en mémoire.
Exemples
Pointeurs sur struct
func (p *Person) IncAge() {
p.age++
}
Le récepteur d'une méthode est un paramètre comme un autre : peut être un pointeur
Le récepteur peut alors être nul ! Parfois utile.
func (p *Person) Name() string {
if p == nil {
return "anonymous"
}
return p.name
}
Accéder aux champs d'une struct pointée se fait normalement (pas de '->'
comme C)
p := new(Person)
p.name = "Johny"
jeff := Person{}
jeff.IncAge() // Valide, même si jeff n'est pas un pointeur
Le récepteur est implicitement référencé si besoin.
"Do not communicate by sharing memory;
instead, share memory by communicating."
- Rob Pike
Goroutines
Définition : fonctionnellement comme un thread, mais plus léger :
démarre avec peu de mémoire et alloue plus si besoin,
peut exister par centaines de milliers,
builtin à Go.
Syntaxe de lancement d'une goroutine
go <statement> // Execute <statement> dans une goroutine
go fetchDocument(documentName)
Exemple
Techniquement :
Go réparti les goroutines sur les threads de l'OS.
Une goroutine bloquée ne bloque pas de thread.
Attention : si main
termine, toutes les goroutines sont tuées.
Channels
Définition : Canal de communication concurrente avec buffer statique.
Syntaxe de création d'un channel
make(chan <type>, <cap>) // Crée un channel de type <type>, avec un buffer de taille <cap>
Syntaxe d'envoi de données sur un channel
<channel> <- <value> // Envoi de <value> de type correspondant sur le channel c
Syntaxe de réception de données d'un channel
<-<channel> // Réception d'une valeur depuis le channel <channel>
Optionnel : si omis, buffer de taille nulle.
Exemple
c := make(chan string, 1)
c <- "Hello, receiver!"
s := <-c
fmt.Println(s) // Hello, receiver!
Bloquant si le buffer est plein (ou de taille nulle si aucune goroutine en attente de lecture)
Bloquant si le buffer est vide (ou de taille nulle si aucune goroutine en attente d'écriture)
Channels
Définition : Canal de communication concurrente avec buffer statique.
Syntaxe de création d'un channel
make(chan <type>, <cap>) // Crée un channel de type <type>, avec un buffer de taille <cap>
Syntaxe d'envoi de données sur un channel
<channel> <- <value> // Envoi de <value> de type correspondant sur le channel c
Syntaxe de réception de données d'un channel
<-<channel> // Réception d'une valeur depuis le channel <channel>
Optionnel : si omis, buffer de taille nulle.
Restrictions sur un channel
chan<- string
Type d'un write-only channel
<-chan string
Type d'un read-only channel
Bloquant si le buffer est plein (ou de taille nulle si aucune goroutine en attente de lecture)
Bloquant si le buffer est vide (ou de taille nulle si aucune goroutine en attente d'écriture)
Channels
Goroutine 1
Goroutine 1
Channels - fermeture
Syntaxe de fermeture d'un channel
close(<channel>)
Syntaxe de réception de fermeture
v, ok := <-c
Seul un envoyeur est autorisé à fermer un channel.
Un channel n'est pas comme un fichier :
Pas besoin de le fermer, sauf pour avertir le récepteur qu'il n'y aura pas d'autre message.
false
ssi le channel a été fermé par un envoyeur.
Channels - range
for <msg> := range <channel> {
// do stuff
}
Permet de lire un channel jusqu'à ce qu'il soit fermé.
for {
<msg>, ok := <channel>
if ok {
// do stuff
} else {
break
}
}
Channels - Select
Syntaxe du select
select {
case msg := <-chan1:
// chan1 had a message, do something with it
case msg := <-chan2:
// chan2 had a message, do something with it
default:
// neither had a message
}
default
est optionnel : si omis, le select sera bloquant.
Permet de
Note : Seule une branche est exécutée, même si plusieurs channels ont un message.
On met donc souvent le select
dans une boucle.
Defer
defer <statement>
Garantie que la connection sera fermée en fin de programme.
Permet de repousser l'exécution d'un statement à la fin de la fonction.
func main() {
conn, err := net.Dial("udp", srvAddr)
defer conn.Close()
// ...
}
Exemple
Exemple
Supposons l'existence de makeQuery(string) Result
func makeQueriesSequential(queries []string) (results []Result) {
for _, q := range queries {
results = append(results, makeQuery(q))
}
return
}
Créons makeQueries
qui gère plusieurs requêtes.
Version simple, séquentielle
Exemple
Supposons l'existence de makeQuery(string) Result
func makeQueriesParallel(queries []string) (results []Result) {
c := make(chan Result)
for _, q := range queries {
go func() { c <- makeQuery(q) }()
}
for _, _ := range queries {
result := <-c
results = append(results, result)
}
return
}
Créons makeQueries
qui gère plusieurs requêtes.
Version parallèle
Exemple
Supposons l'existence de makeQuery(string) Result
func makeQueriesTimeout(queries [string]) (results []Result) {
c := make(chan Result, len(queries))
for _, q := range queries {
go func() { c <- makeQuery(q) }
}
timeout := time.After(80 * time.Millisecond)
for _, _ := range queries {
select {
case result := <-c:
results = append(results, result)
case <-timeout:
fmt.Println("Timed out")
return
}
}
return
}
Créons makeQueries
qui gère plusieurs requêtes.
Version parallèle++
Note : on donne un buffer non-vide, pour que les goroutines n'attendent pas éternellement en cas de timeout.
Exemple 2
var nextId = make(chan int)
func handler(w http.ResponseWriter, q *http.Request) {
fmt.Fprintf(w, "<h1>You have id %v<h1>", <-nextId)
}
func main() {
http.HandleFunc("/next", handler)
go func() {
for i := 0; ; i++ {
nextId <- i
}
}()
http.ListenAndServe("localhost:8080", nil)
}
Pas besoin de compteur partagé, ni de lock, le channel fait tout pour nous !
Web page counting its users
Bonnes pratiques
Aucun état ne doit être partagé entre goroutines.
Gestion d'état dynamique
Un état n'existe donc généralement qu'au sein d'une même goroutine.
type User struct {
name string
age int
}
func handleUsers(newUsers <-chan User, leavingUsers <-chan User) {
users := make(map[string]User)
for {
select {
case u := <-newUsers:
users[u.name] = u
case u := <-leavingUsers:
delete(users, u.name)
}
}
}
func main () {
go handleUsers(/* ... */)
}
La gestion de cet état est sa responsabilité.
Bonnes pratiques
Pour extraire une information de cet état, on peut fournir un channel de retour.
Obtention d'état dynamique
type getUserReq struct {
name string
result chan<- *User
}
func handleUsers(/* ... */, getUser <-chan getUserReq) {
users := make(map[string]User)
for {
select {
/* ... */
cas req := <-getUser:
if u, ok := users[req.name]; ok {
req.result <- &u
} else {
req.result <- nil
}
}
}
}
Bonnes pratiques
Il ne faut qu'un récepteur par channel. (Sinon, lequel lira en premier sera imprévisible)
Communication
Par contre, plusieurs goroutines peuvent écrire en parallèle sur un même channel.
const srvAddr = "127.0.0.1:6000"
func main() {
conn, err := net.Dial("udp", srvAddr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Link stdin and stdout to the connection
go forward(os.Stdout, conn)
forward(conn, os.Stdin)
}
// Links output of src to input of dst
func forward(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}
Client UDP
const srvAddr = "127.0.0.1:6000"
func main() {
// Start listening on srvAddr
addr, _ := net.ResolveUDPAddr("udp", srvAddr)
conn, err := net.ListenUDP("udp", addr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
buf := make([]byte, 1024)
for {
// Get data from client
n, cliAddr, err := conn.ReadFromUDP(buf)
if err != nil {
log.Fatal(err)
}
// Echo data back
s := string(buf[0:n]) + " received from " + cliAddr.String() + "\n"
if _, err := conn.WriteTo([]byte(s), cliAddr); err != nil {
log.Fatal(err)
}
}
}
Serveur UDP
const srvAddr = "127.0.0.1:8000"
func main() {
conn, err := net.Dial("tcp", srvAddr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Link stdin and stdout to the connection
go forward(os.Stdout, conn)
forward(conn, os.Stdin)
}
// Links output of src to input of dst
func forward(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}
Client TCP
const srvAddr = "127.0.0.1:8000"
type clientOutChan chan<- string // per-client outgoing messages
var (
joiningClients = make(chan clientOutChan) // New clients to be registered
leavingClients = make(chan clientOutChan) // Leaving clients to be deleted
broadcast = make(chan string) // Messages destined to all clients
)
func main() {
listener, err := net.Listen("tcp", srvAddr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Broadcast to all connection
go broadcaster()
// Wait for new connections
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
} else {
go handleConn(conn)
}
}
}
func broadcaster() {
clientOuts := make(map[clientOutChan]bool) // All connected clients
for {
select {
case msg := <-broadcast:
for cli := range clientOuts {
cli <- msg
}
case cli := <-joiningClients:
clientOuts[cli] = true
case cli := <-leavingClients:
delete(clientOuts, cli)
close(cli)
}
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
client := make(chan string)
// Forward messages from client channel to connection
go func() {
for msg := range client {
fmt.Fprintln(conn, msg)
}
}()
// Send welcome message to client
who := conn.RemoteAddr().String()
client <- "You are " + who
// Broadcasting arrival of new client
broadcast <- who + " has arrived"
joiningClients <- client
// Broadcasting client messages
input := bufio.NewScanner(conn)
for input.Scan() {
broadcast <- who + " says: " + input.Text()
}
// No more messages, client left.
leavingClients <- client
broadcast <- who + " has left"
}
Serveur TCP
const srvAddr = "127.0.0.1:8000"
type clientOutChan chan<- string // per-client outgoing messages
var (
joiningClients = make(chan clientOutChan) // New clients to be registered
leavingClients = make(chan clientOutChan) // Leaving clients to be deleted
broadcast = make(chan string) // Messages destined to all clients
)
func main() {
listener, err := net.Listen("tcp", srvAddr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Broadcast to all connection
go broadcaster()
// Wait for new connections
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
} else {
go handleConn(conn)
}
}
}
func broadcaster() {
clientOuts := make(map[clientOutChan]bool) // All connected clients
for {
select {
case cli := <-joiningClients:
clientOuts[cli] = true
case cli := <-leavingClients:
delete(clientOuts, cli)
close(cli)
case msg := <-broadcast:
for cli := range clientOuts {
cli <- msg
}
}
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
client := make(chan string)
// Forward messages from client channel to connection
go func() {
for msg := range client {
fmt.Fprintln(conn, msg)
}
}()
// Send welcome message to client
who := conn.RemoteAddr().String()
client <- "You are " + who
// Broadcasting arrival of new client
broadcast <- who + " has arrived"
joiningClients <- client
// Broadcasting client messages
input := bufio.NewScanner(conn)
for input.Scan() {
broadcast <- who + " says: " + input.Text()
}
// No more messages, client left.
leavingClients <- client
broadcast <- who + " has left"
}
Serveur TCP
const srvAddr = "127.0.0.1:8000"
type clientOutChan chan<- string // per-client outgoing messages
var (
joiningClients = make(chan clientOutChan) // New clients to be registered
leavingClients = make(chan clientOutChan) // Leaving clients to be deleted
broadcast = make(chan string) // Messages destined to all clients
)
func main() {
listener, err := net.Listen("tcp", srvAddr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Broadcast to all connection
go broadcaster()
// Wait for new connections
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
} else {
go handleConn(conn)
}
}
}
func broadcaster() {
clientOuts := make(map[clientOutChan]bool) // All connected clients
for {
select {
case cli := <-joiningClients:
clientOuts[cli] = true
case cli := <-leavingClients:
delete(clientOuts, cli)
close(cli)
case msg := <-broadcast:
for cli := range clientOuts {
cli <- msg
}
}
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
client := make(chan string)
// Forward messages from client channel to connection
go func() {
for msg := range client {
fmt.Fprintln(conn, msg)
}
}()
// Send welcome message to client
who := conn.RemoteAddr().String()
client <- "You are " + who
// Broadcasting arrival of new client
broadcast <- who + " has arrived"
joiningClients <- client
// Broadcasting client messages
input := bufio.NewScanner(conn)
for input.Scan() {
broadcast <- who + " says: " + input.Text()
}
// No more messages, client left.
leavingClients <- client
broadcast <- who + " has left"
}
Serveur TCP
const srvAddr = "127.0.0.1:8000"
type clientOutChan chan<- string // per-client outgoing messages
var (
joiningClients = make(chan clientOutChan) // New clients to be registered
leavingClients = make(chan clientOutChan) // Leaving clients to be deleted
broadcast = make(chan string) // Messages destined to all clients
)
func main() {
listener, err := net.Listen("tcp", srvAddr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Broadcast to all connection
go broadcaster()
// Wait for new connections
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
} else {
go handleConn(conn)
}
}
}
func broadcaster() {
clientOuts := make(map[clientOutChan]bool) // All connected clients
for {
select {
case cli := <-joiningClients:
clientOuts[cli] = true
case cli := <-leavingClients:
delete(clientOuts, cli)
close(cli)
case msg := <-broadcast:
for cli := range clientOuts {
cli <- msg
}
}
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
client := make(chan string)
// Forward messages from client channel to connection
go func() {
for msg := range client {
fmt.Fprintln(conn, msg)
}
}()
// Send welcome message to client
who := conn.RemoteAddr().String()
client <- "You are " + who
// Broadcasting arrival of new client
broadcast <- who + " has arrived"
joiningClients <- client
// Broadcasting client messages
input := bufio.NewScanner(conn)
for input.Scan() {
broadcast <- who + " says: " + input.Text()
}
// No more messages, client left.
leavingClients <- client
broadcast <- who + " has left"
}
Serveur TCP
Serveur TCP