Fedora-Fr - Communauté francophone Fedora - Linux

Planet de Fedora-Fr : la communauté francophone autour de la distribution Linux Fedora

A propos

Cette page est actualisée toutes les heures.

Cette page est une sélection de blogs autour de Fedora. Fedora-Fr.org décline toute responsabilité au sujet des propos tenus par les auteurs des blogs de ce planet. Leurs propos sont leur entière responsabilité.

Le contenu de ce planet appartient à leurs auteurs respectifs. Merci de consulter leur blogs pour obtenir les licences respectives.

Mot-clefs : golang

Go, routines et canaux - introduction au langage

Patrice Ferlet

Il y tellement de nouveaux langages, quand un de ceux là sort du lot c'est en général parce qu'il offre des concepts particuliers. Go est un de ceux là, et les concepts de routines, de canaux et de fonctions différées ne sont qu'une infime partie des possibilités que vous pouvez découvrir dans ce langage. Testons un peu ce langage.

Jai la chance daimer mon métier, et dans ce métier on apprend souvent de nouvelles choses, des concepts, des langages... Dans le panel de langages informatique que jai put entrevoir, certains mont vraiment surpris dans leur concept. Le premier dont jai eut beaucoup de mal à mapprocher est le Haskell, et depuis peu je me penche sur Go. Je mempresse donc de vous montrer un peu lintérêt que je vois dans ce langage.

Cest Nicolas Engel, avec qui jai travaillé sur un projet, qui men a reparlé et lenvie de me repencher sur Go sest vite manifesté.

Alors Go cest quoi ?

Ce langage a été créé par une équipe de Google depuis 2010 et a pour vocation de réduire des problématiques que beaucoup de développeurs doivent supporter dans dautres langages. Vous pouvez utiliser le compilateur Go à télécharger ici: http://golang.org/ ou utiliser gcc-go (package Fedora disponible dans les dépots officiels)

Cest un langage compilé, performant et sous licence BSD... La seule complication, et vous allez voir quelle est dominante au début, cest que le concept est vraiment différent de pas mal de langages. Amis développeurs Python, Java, PHP et même C/C++... vous allez être un peu surpris.

La raison principale de la création de ce langage a été motivée par la réduction de problématiques telles que les threads, les sémaphores, les transferts interprocessus, les fractures mémoire, et limpérativité des langages communs. Vous allez voir que des concepts ont été ajoutés à Go pour rendre tout cela vraiment très simple... si si, cest finalement très simple.

Tout dabord, cest un langage fortement typé, les habitués de langages typés dynamiquement vont devoir se faire une raison: il va falloir déclarer vos variables. Ensuite, Go nest pas un langage purement objet mais vous allez facilement pouvoir déclarer des struct avec des méthodes attachées. Jentends dici pas mal dentre vous rire doucement sur un fond de “huhu aujourdhui faire un langage non objet... cest juste revenir dans le passé, autant faire du C”. Je tiens à donner mon avis là dessus, les langages orientés objet ont leurs avantages certains, mais Go est surtout fait pour être performant dans un premier lieu, et le C na rien de vieillot car même votre Linux, votre Mac ou votre Windows est un système dexploitation quasiment entièrement écrit en C.

Enfin, pour ma part, je nai aucun mal à imaginer développer une grosse infrastructure logiciel avec un langage structuré non objet. Et surtout si cest en Go. Dans tous les cas, Go est adressé aux développeurs désirant un langage proche du processeur, un peu comme C/C++ tout en gardant des simplification en terme de threads, sémaphores et gestion mémoire. Bien que Go permette de créer aussi des application web, et se retrouve même dans Google AppEngine. Voyez le guide de démarrage: go appengine.

Bref, passons la polémique trollesque et passons au langage lui même.

On commence par installer le compilateur. Soit vous utilisez gcc-go (dans les dépots) soit vous utilisez le compilateur de google à cette adresse: Golang. Je vous conseille, pour commencer, d'utiliser celui de google et de suivre la manière dont on utilise le runtime. En général, cela revient à faire la commande

go run monfichier.go

Pour le compiler réellement:

go build monfichier.go

Je ne vais pas vous détailler les bases du langage, mais un exemple rapide pour vous donner la syntaxe de base:

package main
 
//fmt est le package de formatage de chaine
import “fmt”
 
//on retrouve une fonction main
func main () {
     fmt.Println(“Hello le mondo !);
}

Bon rien de bien compliqué, un hello world bateau. Vous remarquez donc quon a une notion de package, et donc quon pourra séparer des fonctions et structures dans différent package (ce qui est un plus quand on se réfère au C). Cela ressemble fortement aux espace de nom (namespace) mais lidée est plus claire selon moi.

Petite parenthèse, vous allez rapidement vous rendre compte au fur et à mesure que la plupart des concepts en Go se retrouve dune manière ou dune autre dans C.

Alors ya quoi de nouveau ?

Dabord, comment on déclare une variable ? et bien de deux façons:

// i variable de type int
var i int
i = 4
 
//ou bien
//i est une entier valant 4, le typage est déduit par inférence
i:=4

Pour faire simple, utiliser ":=" (oubliez pas les deux points) pour déclarer en assignant, sinon l'assignation se fait avec un simple "="

Voilà, pas plus pas moins... on va pas parler de pointeur et de structure de suite, mais vous naurez pas de mal à comprendre en lisant la documentation ou en faisant le gotour (voir les liens en fin de billtet)

Je vais vous lister 3 concepts qui mont plut dans ce langage. Le premier est le concept de “goroutine” qui est foncièrement parlant une méthode pour créer des threads sans se prendre la tignasse et se taper le front sur le clavier. Le second concept est le principe de canal (channel) qui permet tellement de choses quon sattriste de ne pas les avoir dans les autres langages (du moins pas sous cette forme). Un canal permet de faire transiter des valeurs dun processus à lautre, de bloquer un processus tant quun autre nest pas terminé et en plus de bufferiser tout ça. Cest le concept de sémaphore (mutex) amélioré dans sa conception. Le troisième concept est la possibilité de différer un appel dans le temps (defer). Cela rend le code lisible à souhait.

Un autre concept, que lon retrouve un peu en python, est le fait de pouvoir retourner plusieurs valeurs de plusieurs types depuis une fonction. Ceux qui nont pas compris lintérêt devrait penser simplement à cela: imaginez une fonction qui retourne un résultat et un code derreur. Plutôt que de devoir retourner une structure, ou un objet pour les langages qui le permettent, on retourne une liste simple de variables.

Bref, passons aux goroutines de suite.

Lappel à une routine se fait simplement via le mot clef “go”. La fonction sera appelée dans un thread (mais dans le même processus, on ne parle pas dun fork ici, mais bien dun thread) et rien dautre nest à faire !

Je ne vous ai pas encore parlé des canaux, donc cet exemple ne va pas fonctionner exactement comme on le veut, mais le concept est là. Ne cherchez pas si ça fonctionne mal chez vous.

package main
 
import "fmt"
 
func hello(you string){
    for i:=0; i<5; i++ {
        fmt.Println("hello you:" + you)
    }
}
 
func main (){
    go hello("Pierre")
    hello ("Paul")
}

Ce qui va vous donner (selon la manière dont les threads sont créé sur votre OS):

hello you:Paul
hello you:Pierre
hello you:Paul
hello you:Pierre
hello you:Paul
hello you:Pierre
hello you:Pierre
hello you:Pierre
hello you:Paul
hello you:Paul

Vous lavez remarqué, jai parfois 3 fois “Pierre” qui apparait, parfois 2 fois “Paul”. Cela est le fait que la routine est géré selon les disponibilités du CPU.

Bon, on passe aux canaux. Là vous risquez de perdre un peu le fil, mais je vous assure que la capacité de ce concept est tout bonnement génial.

Un canal est un type dont le principe est dempiler des valeurs et de bloquer le processus si on cherche à lire dedans, et que celui-ci si il est vide. Cest le principe du FIFO. Lintérêt est sa syntaxe aisée et lisible.

Il faut utiliser la fonction “make” (genre de malloc, mais orienté fabrique ou factory) pour créer un “chan”.

//canal prenant des entiers
c := make (chan int)

Un canal peut prendre n'importe quel type, entier, flottants, chaines... et même des structures.

Ensuite cest plutôt simple... On utilise “<-” pour placer des valeurs dans le flux.

Donc:

//empile un entier (1) dans le canal
c <- 1

Et pour lire le canal

//bloque tant que rien nest empilé dans c
//on peut récupérer la valeur empilée
int val := <-c2
//ou simplement attendre une écriture dans la canal
<-c2

Faisons simple, on va juste comprendre le principe du canal:

package main
 
import "fmt"
 
func routine(a, b chan int){
     i := <-a
     fmt.Printf("je viens de lire dans le canal: %d\n", i);
 
     //'main' attend toujours, je vais écrire dans le canal b
     //pour le débloquer
     b <- 0
}
 
func main(){
    a,b := make(chan int), make(chan int)
 
    //on écrit dans a
    a <- 2
    //on lance une fonction en parallèle
    //qui reçoit les deux canaux en paramètres
    go routine1(a,b)
 
    //on va attendre la fin de la routine
    //la routine doit écrire dans le canal b 
    //pour que cette instruction se débloque
    <- b
    fmt.Println("voilà, j'ai fini, b a été lut")
}

Pour le moment on s'amuse à débloquer des canaux en écrivant dedans. Sachez que vous pouvez fermer un canal avec la fonction "close" mais si vous en avez besoin, c'est que vous être dans un cas particulier. Car en fait, Go n'aime pas que des canaux restent ouverts avec une valeur bloquée et non lut... pensez à traiter correctement les canaux, sinon forcez la fermeture avec "close"

Voyons un autre exemple de code qui va fonctionner avec un canal dentier. Un peu plus salé par contre...

package main
 
import "fmt"
import "time"
 
 
func thread1 ( sem, done chan int) {
    fmt.Println("je suis le thread 1, je commence à bosser")
 
    //grosse fonction qui prend du temps
    time.Sleep(1 * 1e9)
 
    //on débloque le sémaphore
    sem<-0
    //on travaille encore un peu
    time.Sleep(1 * 1e9)
    fmt.Println("je suis le thread 1, je préviens main que je termine")
    //je préviens que la routine est finie
    done<-1
}
 
//voir plus bas (dans thread2), 
//cette méthode va permettre
//une exécution différée
func send(c chan int, num int) {
    c <- num
}
 
func thread2 (sem, done chan int) {
    //autre manière de s'assurer qu'on écrit bien à la
    //fin de la fonction :
    //defer permet de différer l'appel à "send" 
    //à la fin de ma fonction
    defer send(done, 2) 
 
    fmt.Println("je suis le thread 2, je commence à bosser")
 
    //on attend la fin de thread 1
    <-sem
    fmt.Println("je suis le thread 2, on m'a débloqué")
 
    //on bosse encore un peu
    time.Sleep(2 * 1e9)
 
    //quant sleep a terminé, la fonction différée va
    //être exécutée
}
 
func main(){
    //sem va me permettre de gérer les bloquage et débloquage
    //des goroutines
    sem := make (chan int)
 
    //ce canal sera emplilé par la seconde routine pour dire
    //à la fonction main qu'il a terminé
    done := make (chan int)
 
    //on lance les deux routine en parallèle, elle vont communiquer
    //au traver de sem
    //on remplira "done" pour dire que le thread est terminé
    go thread1 (sem, done)
    go thread2 (sem, done)
 
    fmt.Println("je suis dans main, j'attend la fin en lisant le canal 'done'")
 
    //les routines vont écrire dans "done",
    //j'attend que ce soit le cas pour les deux threads
    //donc on attend 2 fois
    //la variable i récupérera la valeur envoyé dans le canal
    var i int
    i = <-done
    fmt.Printf("routine %d vient d'écrire dans le canal 'done'\n", i)
    i = <-done
    fmt.Printf("routine %d vient d'écrire dans le canal 'done'\n", i)
 
    fmt.Println("je suis dans la fonction main, on m'a débloqué")
 
}

Voilà ce que donne mon programme:

je suis dans main, j'attend la fin en lisant le canal 'done'
je suis le thread 1, je commence à bosser
je suis le thread 2, je commence à bosser
je suis le thread 2, on m'a débloqué
je suis le thread 1, je préviens main que je termine
routine 1 vient d'écrire dans le canal 'done'
routine 2 vient d'écrire dans le canal 'done'
je suis dans la fonction main, on m'a débloqué

Ce qui est exactement ce que je voulais. La fonction main sest mise en attente, les deux threads on commencé à travailler en parralèle. La routine 1 déblique la routine 2, mais cette routine 1 fini certaines choses en même temps. Enfin, la fonction “main” remprend la main (non cest pas un jeu de mot)...

Plutôt que de créer une fonction "send" qui n'est utilisée que dans le thread 2, on peut utiliser un système de closure. Car en effet, les fonctions en Go sont des closures. Revoyons le code du thread 2:

func thread2 (sem, done chan int) {
    //autre manière de s'assurer qu'on écrit bien à la
    //fin de la fonction :
    //on crée une closure exécutée en fin de fonction
    //notez que c est local à la closure, la closure prend "done"
    //en argument, donc c correspondra à done
    defer func (c chan int) {
        c <- 2 
   }(done)
 
    fmt.Println("je suis le thread 2, je commence à bosser")
 
    //on attend la fin de thread 1
    <-sem
    fmt.Println("je suis le thread 2, on m'a débloqué")
 
    //on bosse encore un peu
    time.Sleep(2 * 1e9)
 
    //quant sleep a terminé, la fonction différée va
    //être exécutée
}

Vous pouvez donc supprimer la fonction "send". On retrouve ici un concept de fonction "inline" interne à une fonction. En bref, la capacité qu'à Go à vous simplifier la vie est vraiment intéressante.

Bref, là je nai fait quune introduction à Go... je nai pas le recul ni lexpérience nécessaire pour aller plus dans le détail (bien que je mamuse comme un fou avec des tests bourrins). Je vous conseille daller lire le “gotour” ici : GoTour. Je vous conseille de bien vous pencher sur les notions de "range", de retour de plusieurs variables depuis une fonction mais aussi sur les concepts de structure et fonctions de structure. Ce billet ne visait qu'à vous éclairer sur ce que peut vous apporter Go coté parallélisation.

Go est désormais utilisable dans appengine, vous pouvez aussi créer votre propre miniserveur HTTP très facilement avec le package (inclu) "net/http" et que des portage de Gtk, WX etc... ont vu le jour et fonctionnenent.

En ce qui concerne les deux compilateurs, le compilateur (et runtime) Go de Google compile de manière statique mais donne de superbe résultats en terme de performances. Vous pouvez utiliser gcc-go (dans les dépots Fedora du moins) qui vous permettra de compiler dynamiquement vos programmes (le compilateur go faisant de la compilation statique...)

Un dernier point, Go vous permet aussi de récupérer très aisément des librairies C et dutiliser les fonctions impémentées dans ce langage.