Presentation Modèles de concurrence en GO

La concurrence est un facteur clé pour le design de services sur le réseau à haute performance. Les primitives Go pour la concurrence, goroutines et channels (canaux), offrent un moyen pour l'exprimer d'une façon simple et efficace. Pendant cette session nous verrons comment des problèmes complexes peuvent être résolus gracieusement avec du code Go simple

Speakers


PDF: slides.pdf

Slides

Modèles de concurrence en Go

Modèles de concurrence en Go Francesc Campoy Flores Google Mountain View

La concurrence en Go

La concurrence en Go Les gens étaient fascinés par les fonctionnalités de concurrence proposées par Go quand la première version a été annoncée. Questions: Pourquoi supporte-on la concurrence? Mais, c'est quoi la concurrence? D'où vient cette idée? À quoi ça sert? Comment l'utiliser?

Pourquoi gérer la conurrence dans un langage de programmation?

Pourquoi? Regardez autour de vous. Qu'est-ce que vous y voyez? Est-ce que vous voyez un monde ou seulement une chose change à la fois? Ou est-ce que vous voyez un monde complexe avec beaucoup de pièces indépendantes en interaction? Le traitement de données de manière séquentielle ne modélise pas de manière satisfaisante le comportement de notre monde.

C'est quoi la concurrence?

C'est quoi la concurrence? La concurrence est la composition de calculs à exécution indépendante. La concurrence est une façon de structurer un logiciel, en particulier c'est une façon d'écrire du code propre qui interagit correctement avec le monde réel. Ce n'est pas du parallélisme!

La concurrence n'est pas du parallélisme

La concurrence n'est pas du parallélisme La concurrence n'est pas du parallélisme, même si elle le rend plus facile. Si on a seulement un processeur, le programme peut être concurrent mais il ne peut pas être parallèle. Un programme concurrent bien écrit peut être exécuté de façon efficace en parallèle sur un processeur multi-coeur. Cette qualité peut être d'une certaine importance ... Pour plus de différences, allez sur le lien en dessous. golang.org/s/concurrency-is-not-parallelism (http://golang.org/s/concurrency-is-not-parallelism)

Un modèle pour la construction du logiciel

Un modèle pour la construction du logiciel Facile à comprendre. Facile à utiliser. Facile pour raisonner. On n'a pas besoin d'être des experts! (Beaucoup plus agréable que de gérer les détails du parallélisme (threads, semaphores, locks, barrières, etc.))

Histoire

Histoire Pour beaucoup, les fonctionnalités pour la concurrence en Go semblaient nouvelles. Mais elles viennent d'une longue histoire, datant du papier sur les CSP par Hoare en 1978, ou même du langage de commandes de Dijkstra en 1975. D'autres langages ont des fonctionnalités similaires: Occam (May, 1983) Erlang (Armstrong, 1986) Newsqueak (Pike, 1988) Concurrent ML (Reppy, 1993) Alef (Winterbottom, 1995) Limbo (Dorward, Pike, Winterbottom, 1996).

Distinction

Distinction Go est le cadet dans la branche Newsqueak-Alef-Limbo, qui se caractérise par des canaux en tant que des éléments de première classe. Erlang ressemble plus aux CSP originaux, où on communique avec processus par nom plutôt que sur un channel. Les modèles sont équivalents mais expriment des choses différentes. Une analogie: écrire sur un fichier par nom (processus, Erlang) vs. écrire sur un file descriptor (channel, Go).

Exemples basiques

Exemples basiques

Une fonction ennuyeuse

Une fonction ennuyeuse On a besoin d'un exemple qui montre des propriétés intéressantes des primitives pour la concurrence. Pour éviter des distractions, on crée une fonction ennuyeuse. fn enyu(s srn){ uc nuexmg tig fri: 0 ;i+{ o = ; + ftPitnmg i m.rnl(s, ) tm.le(ieScn) ieSeptm.eod } } Run

Un peu moins ennuyeux

Un peu moins ennuyeux Rendons les périodes de temps entre messages imprévisibles (en dessous d'une seconde). fn enyu(s srn){ uc nuexmg tig fri: 0 ;i+{ o = ; + ftPitnmg i m.rnl(s, ) tm.le(ieDrto(adIt(e) *tm.ilscn) ieSeptm.uainrn.nn13) ieMlieod } } Run

Un peu moins ennuyeux

Un peu moins ennuyeux Rendons les périodes de temps entre messages imprévisibles (en dessous d'une seconde). fn enyu(s srn){ uc nuexmg tig fri: 0 ;i+{ o = ; + ftPitnmg i m.rnl(s, ) tm.le(ieDrto(adIt(e) *tm.ilscn) ieSeptm.uainrn.nn13) ieMlieod } } Run

Exécutons!

Exécutons! Une fonction ennuyeuse, tel un bon invité ennuyeux, ne finit jamais. fn mi( { uc an) enyu(enyu!) nuex"nuex" } fn enyu(s srn){ uc nuexmg tig fri: 0 ;i+{ o = ; + ftPitnmg i m.rnl(s, ) tm.le(ieDrto(adIt(e) *tm.ilscn) ieSeptm.uainrn.nn13) ieMlieod } } Run

On peut l'ignorer

On peut l'ignorer Le mot clé go exécute la fonction comme d'habitude, mais sans attendre le retour de l'appel. On crée une nouvelle goroutine. Cette fonctionnalité est similaire au &à la fin d'une commande shell. pcaemi akg an ipr ( mot "m" ft "ahrn" mt/ad "ie tm" ) fn mi( { uc an) g enyu(enyu!) o nuex"nuex" } Run

Ou on peut l'ignorer un peu moins

Ou on peut l'ignorer un peu moins Quand main finit, le programme finit aussi et cause la fin de la fonction ennuyeuse en même temps. On peut attendre un peu, et en même temps montrer que la fonction m i et an e n y u s'exécute en même temps. nuex fn mi( { uc an) g enyu(enyu!) o nuex"nuex" ftPitn"etéot.) m.rnl(J 'cue" tm.le( *tm.eod ieSep2 ieScn) ftPitn"uetenyu,j me vi!) m.rnl(T s nuex e 'n as" } Run

Goroutines

Goroutines C'est quoi une goroutine? C'est une fonction qui s'exécute indépendamment, crée par une instruction g . o Les goroutines ont leur propre pile (stack), qui grandit et rétrécit en fonction des besoins. Elles ne sont pas chères. On peut en avoir des milliers, même des centaines de milliers! Ce ne sont pas des threads. On peut avoir juste un thread pour des milliers de goroutines. Les goroutines sont multiplexées dynamiquement sur des threads de façon à les avoir toutes en exécution. Mais si tu penses aux goroutines en tant que des threads pas chers, t'es vraiment pas loin.

Communication

Communication On a un peu triché avec les exemples précédents: la fonction main ne pouvait pas voir l'output de l'autre goroutine. L'autre goroutine imprimait sur l'écran, et on faisait semblant d'avoir une conversation. Les vraies conversations ont besoin de communication.

Channels (canaux)

Channels (canaux) Les channels en Go offrent une connexion entre deux goroutines, et leur permettent de communiquer. / Dcaaine iiilsto. / élrto t ntaiain vrcca it a hn n c=mk(hnit aeca n) / o / u c: mk(hnit = aeca n) / Eviduevlu sru canl / no 'n aer u n hne. c< 1 / Rcpinduevlu du canl / éeto 'n aer 'n hne. / L "lce mnr l drcind fu d dnés / a fèh" ote a ieto u lx e one. vle=Utilisation des channels Utilisation des channels Un channel connecte les goroutine main et ennuyeux pour qu'elles communiquent. fn mi( { uc an) c: mk(hnsrn) = aeca tig g enyu(enyu!,c o nuex"nuex" ) fri: 0 i<5 i+{ o = ; ; + / Lepeso d rcpinetuevlu. / 'xrsin e éeto s n aer ftPit(T ds %\" Synchronisation Synchronisation Quand la fonction main exécute < celle attend qu'une valeur soit envoyée. -, D'une façon similaire, quand la fonction e n y u exécute c - a u , elle nuex Buffered channels (canaux à tampon mémoire) Buffered channels (canaux à tampon mémoire) Note pour les experts: Les channels Go peuvent aussi être crées avec un tampon mémoire. Ceci enlève la synchronisation. Les buffered channels sont très similaires aux mailboxes d'Erlang. Les buffered channels peuvent être importants pour quelques problèmes mais le raisonnement est plus subtil. On n'aura pas besoin d'eux aujourd'hui.

L'approche Go

L'approche Go "Don't communicate by sharing memory, share memory by communicating." "Ne communique pas en partageant de la mémoire, partage de la mémoire en communiquant."

Modèles

Modèles

Générateur: une fonction qui retourne un channel

Générateur: une fonction qui retourne un channel Les channels sont des valeurs à part entière, tels que les strings ou les entiers. c: enyu(enyu!)/ Fnto qirtun u canl = nuex"nuex" / ocin u eore n hne. fri: 0 i<5 i+{ o = ; ; + ftPit(Yusy %\" Les channels en tant que point d'accès à un service Les channels en tant que point d'accès à un service Notre fonction ennuyeuse retourne un channel qui nous permet de communiquer avec l'ennuyeux service qu'elle offre. Nous pouvons avoir plus d'une instance du service. fn mi( { uc an) je: enyu(Je) o = nuex"o" an: enyu(An) n = nuex"n" fri: 0 i<5 i+{ o = ; ; + ftPitnMultiplexage Multiplexage Ces programmes font que Joe et Ann comptent à tour de rôle. On peut utiliser un multiplexeur (fan-in) pour laisser parler le premier à être prêt. fn fnnipt,ipt Multiplexage (Fan-in) Multiplexage (Fan-in)

Multiplexage

Multiplexage Ces programmes font que Joe et Ann comptent à tour de rôle. On peut utiliser un multiplexeur (fan-in) pour laisser parler le premier à être prêt. fn fnnipt,ipt Réinstaurer l'ordre Réinstaurer l'ordre On peut envoyer un channel dans un channel, et faire que la goroutine attende son tour. On reçoit tous les messages, puis on permet aux goroutines d'envoyer des nouveaux par leur propre channel. D'abord on définit un type de message qui contient le channel pour la réponse. tp Msaesrc { ye esg tut sr srn t tig wi ca bo at hn ol }

Réinstaurer l'ordre

Réinstaurer l'ordre Chaque émeteur doit attendre le permis de continuer. fri: 0 i<5 i+{ o = ; ; + mg : Select Select Une structure de contrôle uniquement pour la concurrence. La raison pour laquelle les channels et les goroutines font partie du langage.

Select : utilisation

Select L'instruction select offre une autre façon de gérer plusieurs channels. C'est comme un switch, mais chaque 'case' est une communication: - Tous les channels sont évalués. - Select bloque jusqu'à ce qu'une des opérations puisse être exécutée, puis il l'exécute. - Si plusieurs opérations sont prêtes, on en choisit une pseudo-aléatoirement. - Le cas default, si présent, est exécuté si aucune autre opération n'est prête. slc { eet cs v : Encore le multiplexeur Encore le multiplexeur On peut réécrire notre fonction fanIn avec seulement une goroutine. Avant: fn fnnipt,ipt Fan-in avec select Fan-in avec select On peut réécrire notre fonction fanIn avec seulement une goroutine. Après: fn fnnipt,ipt Timeout avec select Timeout avec select La fonction t m . f e retourne un channel qui bloque pour une période de ieAtr durée donnée. Aprés cette durée, le channel reçoit le temps actuel, une seule fois. fn mi( { uc an) c: enyu(Je) = nuex"o" fr{ o slc { eet cs s: Timeout pour la conversation entière avec select Timeout pour la conversation entière avec select Crée le temporisateur une fois, en dehors de la boucle, pour faire timeout de la conversation entière. (On avait un timeout par message au paravant.) fn mi( { uc an) c: enyu(Je) = nuex"o" tmot: tm.fe( *tm.eod ieu = ieAtr5 ieScn) fr{ o slc { eet cs s: Timeout avec select Timeout avec select La fonction t m . f e retourne un channel qui bloque pour une période de ieAtr durée donnée. Aprés cette durée, le channel reçoit le temps actuel, une seule fois. fn mi( { uc an) c: enyu(Je) = nuex"o" fr{ o slc { eet cs s: Timeout pour la conversation entière avec select Timeout pour la conversation entière avec select Crée le temporisateur une fois, en dehors de la boucle, pour faire timeout de la conversation entière. (On avait un timeout par message au paravant.) fn mi( { uc an) c: enyu(Je) = nuex"o" tmot: tm.fe( *tm.eod ieu = ieAtr5 ieScn) fr{ o slc { eet cs s: Le channel quit Le channel quit On peut retourner l'interaction et demander à Joe d'arrêter de parler quand on est fatigué de l'entendre. qi : mk(hnbo) ut = aeca ol c: enyu(Je,qi) = nuex"o" ut fri: rn.nn1) i> 0 i-{ o = adIt(0; = ; ftPitnLe channel quit Le channel quit On peut retourner l'interaction et demander à Joe d'arrêter de parler quand on est fatigué de l'entendre. qi : mk(hnbo) ut = aeca ol c: enyu(Je,qi) = nuex"o" ut fri: rn.nn1) i> 0 i-{ o = adIt(0; = ; ftPitnRéception par le channel quit Réception par le channel quit Comment sait-on que Joe a fini? On peut attendre qu'il nous le dise: en utilisant quit une deuxième fois. qi : mk(hnsrn) ut = aeca tig c: enyu(Je,qi) = nuex"o" ut fri: rn.nn1) i> 0 i-{ o = adIt(0; = ; ftPitnLe jeu du téléphone, avec des Gophers Le jeu du téléphone, avec des Gophers

Le jeu du téléphone, avec des Gophers : le code

Le jeu du téléphone, avec des Gophers fn flf,rgtca it { uc (et ih hn n) lf < 1+Un langage pour les systèmes logiciels Un langage pour les systèmes logiciels Go a été crée pour écrire des systèmes logiciels. Voyons comment les fonctionnalités de concurrence nous aident.

Exemple: Google Search

Exemple: Google Search Q: Que fait Google? A: Pour une requête donnée, crée une page avec des résultats (et quelques annonces). Q: Comment on fait pour obtenir les résultats? A: On envoie la requête au moteurs Web, Image, YouTube, Maps, News, etc., puis on les mixe. Comment implémenter ça?

Google Search: Un faux framework

Google Search: Un faux framework On peut simuler les fonctions de recherche, comme on fait avec les conversations. vr( a Wb =fkSac(wb) e aeerh"e" Iae=fkSac(iae) mg aeerh"mg" Vdo=fkSac(vdo) ie aeerh"ie" ) tp Sac fn(ur srn)Rsl ye erh ucqey tig eut fn fkSac(idsrn)Sac { uc aeerhkn tig erh rtr fn(ur srn)Rsl { eun ucqey tig eut tm.le(ieDrto(adIt(0) *tm.ilscn) ieSeptm.uainrn.nn10) ieMlieod rtr Rsl(m.pit(Rsla % pu %\" kn,qey) eun eutftSrnf"éutt s or qn, id ur) } }

Google Search: Testons le framework

Google Search: Testons le framework fn mi( { uc an) rn.edtm.o(.nxao) adSe(ieNw)UiNn() sat: tm.o( tr = ieNw) rsls: Gol(gln" eut = oge"oag) easd: tm.ic(tr) lpe = ieSnesat ftPitnrsls m.rnl(eut) ftPitneasd m.rnl(lpe) } Run

Google Search 1.0

Google Search 1.0 La fonction G o l reçoit une requête et retourne une slice de R s l s oge eut (qui sont des strings). G o l utilise les recherches W bI a eet V d oen série, en oge e, mg ie rajoutant les résultats à la slice r s l s eut. fn Gol(ur srn)(eut [Rsl){ uc ogeqey tig rsls ]eut rsls=apn(eut,Wbqey) eut pedrsls e(ur) rsls=apn(eut,Iaeqey) eut pedrsls mg(ur) rsls=apn(eut,Vdoqey) eut pedrsls ie(ur) rtr eun } Run

Google Search 2.0 avec la concurrence

Google Search 2.0 Appelons W bI a eet V d oconcurremment, et attendons les résultats. e, mg ie Pas de verrous. Pas de moniteurs. Pas de callbacks. fn Gol(ur srn)(eut [Rsl){ uc ogeqey tig rsls ]eut c: mk(hnRsl) = aeca eut g fn( {c< Wbqey }) o uc) - e(ur) ( g fn( {c< Iaeqey }) o uc) - mg(ur) ( g fn( {c< Vdoqey }) o uc) - ie(ur) ( fri: 0 i<3 i+{ o = ; ; + rsl : Google Search 2.1 Google Search 2.1 N'attendons pas les serveurs trop lents. Pas de verrous. Pas de moniteurs. Pas de callbacks. c: mk(hnRsl) = aeca eut g fn( {c< Wbqey }) o uc) - e(ur) ( g fn( {c< Iaeqey }) o uc) - mg(ur) ( g fn( {c< Vdoqey }) o uc) - ie(ur) ( tmot: tm.fe(0*tm.ilscn) ieu = ieAtr8 ieMlieod fri: 0 i<3 i+{ o = ; ; + slc { eet cs rsl : Comment éviter les timeouts Comment éviter les timeouts Q: Comment éviter la perte de résultats par les serveurs lents? A: Repliquer les serveurs. Envoyer les requêtes à plusieurs instances, et utiliser seulement la première réponse. fn Frtqeysrn,rpia ..erh Rsl { uc is(ur tig elcs .Sac) eut c: mk(hnRsl) = aeca eut sacRpia: fn( it {c< rpia[]qey } erhelc = uci n) - elcsi(ur) fri: rnerpia { o = ag elcs g sacRpiai o erhelc() } rtr Utiliser la première réponse Utiliser la première réponse fn mi( { uc an) rn.edtm.o(.nxao) adSe(ieNw)UiNn() sat: tm.o( tr = ieNw) rsl : Frt"oag, eut = is(gln" fkSac(rpia1) aeerh"elc ", fkSac(rpia2) aeerh"elc ") easd: tm.ic(tr) lpe = ieSnesat ftPitnrsl) m.rnl(eut ftPitneasd m.rnl(lpe) } Run

Google Search 3.0

Google Search 3.0 Réduction de la latence en utilisant des serveurs de recherche répliqués. c: mk(hnRsl) = aeca eut g fn( {c< Frtqey Wb,Wb)}) o uc) - is(ur, e1 e2 ( g fn( {c< Frtqey Iae,Iae)}) o uc) - is(ur, mg1 mg2 ( g fn( {c< Frtqey Vdo,Vdo)}) o uc) - is(ur, ie1 ie2 ( tmot: tm.fe(0*tm.ilscn) ieu = ieAtr8 ieMlieod fri: 0 i<3 i+{ o = ; ; + slc { eet cs rsl : Et tojours... Et tojours... Pas de verrous. Pas de moniteurs. Pas de callbacks.

Résumé du chemin parcouru

Sommaire En juste quelques transformations simples on a utilisé les primitives de concurrence en Go pour convertir un système: lent séquentiel sensible aux erreurs en un système rapide concurrent répliqué robuste.

D'autres exemples

D'autres exemples Il y a beaucoup d'autres façons d'utiliser ces outils, beaucoup sont presentées ailleurs. Chatroulette: golang.org/s/chat-roulette (http://golang.org/s/chat-roulette) Load balancer: golang.org/s/load-balancer (http://golang.org/s/load-balancer) Crible des nombres premiers concurrente: golang.org/s/prime-sieve (http://golang.org/s/prime-sieve) Séries entières concurrente (par McIlroy): golang.org/s/power-series(http://golang.org/s/power-series)

Il faut pas trop en faire

Il faut pas trop en faire C'est amusant d'utiliser ces idées. Les goroutines et les channels c'est des grandes idées. Des bons outils pour la programmation. Mais ... parfois, tout ce dont on a besoin c'est un compteur de références. Go fournit "sync" et "sync/atomic" avec des mutex, sémaphores, etc. Il faut toujours choisir le bon outil pour chaque tâche.

Conclusions

Conclusions Les goroutines et les channels rendent facile à exprimer les opérations liées à plusieurs inputs plusieurs outputs timeouts erreurs Et ils sont amusants à utiliser!

Quelques liens

Quelques liens Go Home Page: golang.org(http://golang.org) Go Tour (apprend Go depuis ton navigateur) tour.golang.org(http://tour.golang.org) Documentation de la standard library: golang.org/pkg(http://golang.org/pkg) Plein d'autres articles: golang.org/doc(http://golang.org/doc) Concurrency is not parallelism: golang.org/s/concurrency-is-not-parallelism (http://golang.org/s/concurrency-is-not-parallelism)

Thanks!

Thanks!

Thank you

Thank you Francesc Campoy Flores Google Mountain View @campoy83 (http://twitter.com/campoy83) (mailto:campoy@google.com) (http://golang.org)