• Aucun résultat trouvé

Concurrence et continuations en OCaml

N/A
N/A
Protected

Academic year: 2021

Partager "Concurrence et continuations en OCaml"

Copied!
17
0
0

Texte intégral

(1)

HAL Id: hal-00665918

https://hal.inria.fr/hal-00665918

Submitted on 3 Feb 2012

HAL

is a multi-disciplinary open access archive for the deposit and dissemination of sci- entific research documents, whether they are pub- lished or not. The documents may come from teaching and research institutions in France or abroad, or from public or private research centers.

L’archive ouverte pluridisciplinaire

HAL, est

destinée au dépôt et à la diffusion de documents scientifiques de niveau recherche, publiés ou non, émanant des établissements d’enseignement et de recherche français ou étrangers, des laboratoires publics ou privés.

Christophe Deleuze

To cite this version:

Christophe Deleuze. Concurrence et continuations en OCaml. JFLA - Journées Francophones des

Langages Applicatifs - 2012, Feb 2012, Carnac, France. �hal-00665918�

(2)

Conurrene et ontinuations en OCaml

C. Deleuze

Laboratoirede Coneption etd'IntégrationdesSystèmes

50 rueBarthélémy de Laemas

26902ValeneCedex 09

Frane

hristophe.deleuzelis.grenoble-inp.fr

Résumé

Nous dérivonsetmettonsen÷uvrediérentesméthodespourfournir laonurrene légère

en OCaml, en style diret ou indiret. Nous montrons que toutes es approhes font appel,

expliitement ou impliitement, à la notion de ontinuation. Nous essayons de préiser leurs

relations et omparons leur performanes surdeux appliations onurrentes simples. Presque

touteslesmisesen÷uvrelégèressont trèslargementmeilleuresquelesthreadssystèmeoudela

mahinevirtuelle.

1. Introdution

On peut dénir la onurrene omme une propriété d'un système dans lequel plusieurs ls

d'éxéution existent et progressent simultanément. Ces threads représentent des traitements entre

lesquels il n'y a pas de dépendane temporelle par défaut : les dépendanes sont exprimées

expliitementpardesopérationsdesynhronisation.

Lessystèmesd'exploitationfournissentetteabstrationmaisaveunoûtrelativementimportant,

en mémoire utilisée par thread et en temps pour hanger de ontexte. L'ordonnanement est

généralementpréemptifequirenddéliatlepartagededonnéesentrelesthreads.

Il est possible d'obtenir une onurrene plus légère en la mettant en ÷uvre au niveau de

l'appliation. De nombreuses approhes ont été proposées ave généralement un ordonnanement

oopératif. Dans et artile nous montrons que toutes es approhes font appel, expliitement ou

impliitement, àlanotionde ontinuation.Nousessayonsde préiserleurs relationset deomparer

leursperformanesautraversdemisesen÷uvre simplesdansleadredulangageOCaml.

2. Un modèle simple de onurrene

Nousproposonsunmodèlesimpledeonurrene,déniparlesprimitivessuivantes:

spawn prend un thunk et réeun nouveau thread àexéuter.Celui-i ne ommene sonexéution

quesi/quandlaprimitivestart aétéinvoquée.

yield suspendlethread,permettantauxautresdes'exéuter.

halt terminelethread.Sitouslesthreads sontterminés,start retourne.

start démarrelesthreadsréésparspawn et bloquejusqu'àe qu'ilssoienttousterminés.

stop stoppedenitivementtouslesthreads.start retourne.

(3)

Lesthreads peuventommuniquer àl'aide deMVars.Introduites enHaskell[14℄,les MVars sont

desvariablesmutablespartagéesquipermettentlaommuniationetlasynhronisationentrethreads.

Ondénitlestroisopérationssuivantes:

make mvar réeunenouvelleMVarvide.

take mvar retirelavaleurd'uneMVar.

put mvar plaeunevaleurdansune MVar.

Une MVar peutontenirune valeurouêtre vide.Lesopérationstake et put bloquentsilaMVar

est respetivement vide ou pleine. Pour simplier nous onsidérons que pour haqueMVar un seul

threadsouhaiteérireet unseulthreadsouhaitelireàunmomentdonné.

3. Deux appliations

Nousdérivonsi-dessousdeuxexemplesd'appliationsquinouspermettrontd'évaluerlestylede

programmationrequisparnotremodèleethaunedesesmisesen÷uvre.Ellesservirontaussiàfaire

quelquesomparaisonsdeperformanes.

3.1. Crible d'Ératosthène

Notrepremièreappliation estleribled'Ératosthène.Cetteversiononurrente estunlassique

notamment présentée dans [7℄ (qui indique que sa première mention est dans [13℄). Une variante

apparaît également dans [6℄. Le programme est struturé en une haîne de threads éhangeantdes

messages:

integers estlegénérateur,il émettouslesentiersàpartirde2,

lter n relaielesnombresqu'ilreçoits'ilsnesontpasmultiplesdesonparamètren,

sift réeetinsèreunnouveaultredanslahaîneàhaquenombrereçu,

output ahelesnombresqu'ilreçoit.

integers

mi sift

mo output

integers

mi filter 2

m sift

mo output

integers

mi filter 2

m filter 3

m’ sift

mo output

thread

mvar

spawn create

spawn cr.

Figure 1Leribleonurrent

Ainsileribleseonstruitommeunehaînedethreadsfilterenadrésparungénérateur(integers)

surlagauheetunétendeur(sift)suivid'unonsommateur(output)surladroite.Lesmessagessont

éhangésau moyen deMVars.Lagure 1montre lesthreads audémarrageet après que lepremier

puisledeuxièmenombrespremiersaientététrouvés.

Leode,visiblegure12enstylediret etindiret,estpartiulièrementsimple.

(4)

3.2. Tri onurrent

Notre seond exempleest un trionurrentdérit dans [6℄, qui explique quele tribulle et letri

parinsertionsontdesversionsséquentialisées deetalgorithme.

L'algorithmeutiliseun réseaudethreads comparateur très simples,haunutilisé pourtrier une

paire de valeurs depuis deux MVars d'entrée vers deux MVars de sortie. Un omparateur ave les

entréesx et y et lessorties hi and lo est montrégure 2(a). Lagure2(b) montre unréseau pour

trierunelistede4valeurs.

PSfragreplaements

x y

hi lo

(a)Comparateur

PSfragreplaements

x0

x1

x2

x3

r0 r1 r2 r3

(b)Réseaudetri

Figure2Trionurrent

LesMVarsn'apparaissentpassurlagure,ellesstokerontlesvaleursinitialesetnalesetserviront

pourlaommuniationentrelesomparateurs.Nousnemontronspasleodeparmanquedeplae.

4. Réalisations

Notre ordonnaneur sera une simple boule extrayantles threads d'une le FIFO (runq) et les

exéutant. Nous pouvonsd'ores et déjà implémenter les primitives start et stop. L'implémentation desautresprimitivesdépendradehaqueréalisation.

letrunq = Queue.create ()

letenqueue t = Queue.push t runq

letdequeue () = Queue.take runq

exeptionStop

letstop () = raise Stop

letstart () =

try

whiletruedo

dequeue () ()

done

withQueue.Empty | Stop → ()

4.1. Réalisations en style diret

Dansleasde laprogrammationenstyle diret, lesprimitivesaurontlessignaturesmontréessur

lagure3.Certainesdeesopérationssontpotentiellementbloquantesmaisseprésententommedes

fontionsordinaires.Leodeduribleest montrégure12(a).

Lesupportstandardd'OCamlpourlesthreads(systèmeouVM)peutêtreutilisépourréaliserles

primitivesaveessignatures.Nousnedérironspasetteréalisationquinousservirauniquementde

référenepouromparerlesperformanesdesréalisationslégères.

(5)

valyield : unitunit

valspawn : (unitunit) → unit valhalt :unitunit

valstart :unitunit valstop :unitunit

typeαmvar

valmake mvar :unit → αmvar

valtake mvar : αmvar → α

valput mvar : αmvar → α → unit Figure3Signaturesdesprimitivespourlestylediret

Pour suspendre un thread et le réativer plus tard, nous devons pouvoir sauver la situation

ourante du thread. Cela orrespond à la notion de ontinuation, qui représente à un point de

l'exéutiond'unefontione quiresteàexéuter[2℄, 'estàdireleontextedel'éxéution.

Laprimitiveallwithurrentontinuation (oucall/cc)aétéintroduiteparlelangageSheme[8℄.

Celle-i réaliseune opie de (ouapture) laontinuationatuelle et laréie (la rend manipulable

parle programme)en une valeurde typeα cont.1 Les ontinuationspeuventainsi être manipulées expliitement par le programme omme n'importe quelle autre valeur. Une telle ontinuation de

première lasse peut être lanée (throw) ave un paramètre de type α auquel as elle érase la

ontinuation ourante, de façon que l'éxéution reprenne au point la ontinuation avait été

apturée.Ceipermetdemanipulerleotdeontrleduprogrammeet enpartiulierd'introduirela

notiondethreads.

Nous avons réaliséune miseen ÷uvre baséesur call/cc similaireàelle dérite dans[4℄mais ne

laprésenteronspas ii: laseule miseen÷uvre existante decall/cc pourOCamlest dériteparson

auteurommetrèsnaïveavedesperformanesterribles [11℄.

Nous utiliseronslabibliothèqueaml-shift[15℄ qui oredesontinuations délimitées. Une telle

ontinuationestunpréxedeequiresteàexéuter,représentéeparuneportiondélimitéeduontexte.

Unetelleontinuation(aussiappeléepartielle,omposable,orsousontinuation)retourneunevaleur

etpeutdonêtreréutilisée etomposée.

La littérature dénit un ertain nombre d'opérateurs assoiés. L'idée générale est qu'une telle

ontinuation est onstruite en ommençant par pousser un délimiteur (ou prompt) sur la pile puis

en apturant la ontinuation jusqu'à un prompt. Dans ette bibliothèque push prompt pousse un

délimiteur, tandis que take subcont retire de la pile le fragmentjusqu'au prompt inlu et retourne

le fragment (sans le prompt) sous forme d'une valeurde type (α, β) subcont2 α est le type de

la valeuràpasser aulaner de la ontinuation,β étantelui retourné par laontinuation. Enn,

push subcont pousse uneontinuationsurlapile(ie lalane).

Ainsi, pour suspendre un thread nous devons apturer sa ontinuation. Pour le réativer nous

devons pousser le prompt et la ontinuation. Nous hoisissons d'enapsuler immédiatement la

ontinuation apturée dans une fontion qui poussera le prompt et la ontinuation, e qui est le

omportementdel'opérateurshift0 (elui-iretirelepromptdelapileetl'inlutdanslaontinuation apturée)[9℄.Ainsil'ordonnaneurdoitsimplementexéuterlafontionpourrelanerlethread(voir

ledequeue () () danslaboulestart plushaut).

letprompt = new prompt ()

letshift0 p f = take subcont p (funsk () →

(f (func → push prompt subcont p sk (fun() → c))))

1. En Sheme, les ontinuations sont réiées sous forme de fontions. Appliquer la fontion revient à laner la

ontinuation.Ladesription quenous donnonsiiorrespond plusàe quipeut êtremisen÷uvre dansunlangage

typéstatiquementommeOCaml.

2. Ceomportementorrespondàl'opérateurcontrol0 [9℄.

(6)

letyield () = shift0 prompt (funf → enqueue f)

lethalt () = shift0 prompt (funf → ())

halt nettoie la pile ensupprimant leprompt et un éventuelreste de ontexte.La dénition de shift0 donnéei-dessusestuneversionoptimisée3 de

letshift0 p f = take subcont p (funsk () → (f (func → push prompt p

(fun() → push subcont sk (fun() → c)))))

spawnenapsulesonargumentdansunefontionquiommeneparpousserlepromptetsetermine

enappelanthalt defaçonàassurerquelepromptsoitretirédelapilequandlethreadtermine.

letspawn t = enqueue (fun() → push prompt prompt (fun() → t(); halt ()))

UneMVarestunsimplestrutavetroisvaleursdetypeoption:lavaleurstokéedanslaMVar,

le thread bloqué sur une opération take mvar, le thread bloqué sur une operation put mvar ave

lavaleurqu'il veut plaer. Quandun thread bloque, sa ontinuationest apturéeet enapsuléepar

shift0 puisstokéedanslehampapproprié.Lafontionserareplaéedanslaled'exéution quand

lethread seraréativé.Lesfailwith orrespondentànotrehypothèsesimpliatried'unseul thread souhaitantlireouériredansuneMVardonnéeàunmomentdonné.

typeαt = α → unit

typeαmvar = {mutablev:αoption;

mutableread: αt option;

mutablewrite: (unit t × α)option}

letmake mvar () = {v=None; read=None; write=None }

letput mvar out v =

mathout with

| {v=Some v; read = ; write=None } →

shift0 prompt (funf → out.write ← Some (f,v))

| {v=None; read=Some r; write=None } →

out.read ← None; enqueue(fun() → r v)

| {v=None; read=None; write=None } → out.v ← Some v

| {v= ; read= ; write=Some } → failwith "put mvar"

lettake mvar inp =

mathinp with

| {v=Some v; read=None; write=None } → inp.v ← None; v

| {v=Some v; read=None; write=Some(c,v)} → inp.v ← Some v; inp.write ← None; enqueue c; v

| {v=None; read=None; write= } → shift0 prompt (funf → inp.read ← Some f)

| {v= ; read=Some ; write= } → failwith "take mvar"

4.2. Réalisations en style indiret

Dans le style indiret le ode des appliations est rédigé de manière à rendre les ontinuations

expliites (sous forme defermeture)à haquepoint deoopération. Ainsiles ontinuationspeuvent

êtremanipuléessansavoirreoursàuneprimitivedeapture.

3. Enfaitlaversionnonoptimiséeprésenteunefuitedemémoire(voir[10,AppendixB℄).

(7)

Voyez le ode du thread sift du rible sur la gure 12(b). Nous notons >>= (appelé bind)

l'opérateur deomposition séquentielle.

4

Ce opérateurest présentaux pointsde oopération,entre

uneopérationpotentiellementbloquanteetsaontinuation.Cetteontinuationestunefermeturequi

sera exéutée quand l'opération bloquante sera terminée, elle en reevra le résultat en paramètre.

Ce style impose de onvertir les boules impérativesen fontionsréursivessi ellesontiennent une

opérationbloquante.Nousdénironsuneopérationsupplémentaireenstyleindiret:skip,l'opération vide.

Nousdérivonsdans lasuitelaréalisationde notremodèleave lesstylestrampoline, monadique

(basé sur des ontinuations ou des promesses) et parévénements. Tous sont des variantes du style

indiret.

4.3. Style trampoline

Danslestyletrampoline[5℄haquefontionbloquante reçoitsa ontinuationenparamètre.C'est

une variante du style passage de ontinuation (CPS, [17℄) les ontinuations ne sont rendues

expliitesqu'auxpointsdeoopération.

La gure 4 montre les signatures des opérations. Chaque opération potentiellement bloquante

prendenparamètresaontinuation,une fontionàexéuterquandl'opérationaétéréalisée.

valskip : (unitunit) → unit

val(>>= ) : ((α → unit) → unit) → (α → unit) → unit valyield : (unitunit) → unit

valspawn : (unitunit) → unit valhalt :unitunit

valstart :unitunit valstop :unitunit

typeαmvar

valmake mvar :unit → αmvar

valtake mvar : αmvar → (α → unit) → unit

valput mvar : αmvar → α → (unitunit) → unit Figure4Signaturesdesprimitivespourlestyletrampoline

Par exemple l'opération yield (i-dessous àgauhe) prend en argument sa ontinuation, 'est à direlafontionàexéuterquand lethread seraréativé.Onpeutobtenirunesyntaxeplusplaisante

(i-dessousàdroite)sil'ondénit l'opérateurinxe >>= ommeappliquantsonpremierargument

au deuxième (la ontinuation) 5

et si nous adoptons une indentation reétant l'idée d'exéution en

séquene.

print string "hoho";

yield (fun() → print string "haha";

yield (fun() →

...

))

let(>>=)inst (k :α → unit) :unit =inst k

...

print string "hoho";

yield >>=fun() → print string "haha";

yield >>=fun() →

...

Rendrelesontinuationsexpliitesn'estpasnéessairementaussiintrusifqu'onpourraitlepenser.

Dansleodeduribleellesn'apparaissentenfaitjamais(arlesfontionssontréursivesinnies).

Ellesnedoiventêtrementionnéesquequandononstruitdesopérationsbloquantesomplexes,omme

4. C'estunempruntàlasyntaxedesmonadesmaisnereprésentepasnéessairementl'opérateurbinddesmonades

ommenousleverrons.

5. LesprogrammeursHaskellpenserontàl'opérateur$.

(8)

les deux exemples i-dessous. Le premier abstrait l'opération de suspension trois fois d'alée, le

deuxièmeletransfertd'unevaleurd'uneMVar àuneautre:

letyield3 k =

yield >>=fun() → yield >>=fun() → yield >>=

k

lettransfer mvar m1 m2 k = take mvar m1 >>=funv → put mvar m2 v >>=

k

Réalisation Une fontion bloquante, omme yield ou take mvar, reçoit sa ontinuation en paramètre,ellepeutl'exéuterimmédiatementou,sielledoitbloquer,lastokeravantderetourner.

Leodeesttrèsprohedeeluiutiliséavelesapturesdeontinuationsdélimitées:ladiérene

estquenousn'avonspasàlesapturerpuisqu'ellessontfourniesexpliitement.

letskip k = k ()

letyield k = enqueue k

lethalt () = ()

letspawn t = enqueue t

letclose k =fun() → k (fun → ())

lettake mvar inp k =

mathinp with

| {v=Some v; read=None; write=None } → inp.v ← None; k v

| {v=Some v; read=None; write=Some(c,v)} → inp.v ← Some v; inp.write ← None; enqueue c; k v

| {v=None; read=None; write= } → inp.read ← Some(k)

Unmotsurspawn:omposerdesfragmentsave>>=produitgénéralementunefontionouverte, prenantune ontinuation,qu'ilfautluifournirandelafermer avantdel'exéuterommethread.

Cependant les threads dénis pour le rible sont des réursions innies qui ne prennent pas untel

paramètre.Nousséparons donlafermeturede lafontiondulanementduthread (fontions close

etspawn).

4.4. Monade de ontinuation

Lamonadedeontinuationestdénie dans[3℄.Lesmonadessontutilespourgérerleseetsdans

unenvironnementfontionnelpur[14℄.Unemonadeestuntypeαt quiore(aumoins)lesprimitives return et bind (notéommeopérateurinxe>>=)montréesgure5.Commelesuggèrentlestypes, return v onstruitune valeurmonadiquede typeαt ontenant v etm >>= f ouvre m pour

extrairelavaleur v,ladonneàf etretournelavaleurmonadiqueretournéeparf.

Iinouspouvonsdénirtypeαt = (α → unit) → unit,une valeurdetypeαtestunefontion

prenantuneontinuation detypeα → unit.Nouspouvonsalorsvoirlesfontions bloquantesomme retournantune valeurdetypeαt (gure 5).

L'opérateur bind est un peu plus ompliqué que elui utilisé dans le style trampoline. Il prend enhargelagestiondesontinuationsqui n'ontplusàapparaîtreexpliitementdans laomposition

de fragments(nous avonshangé très légèrement la dénition de yield an de lui faireprendre un

argument()):

letyield3 () =

yield () >>=fun() → yield () >>=fun() → yield ()

lettransfer mvar m1 m2 = take mvar m1 >>=funv → put mvar m2 v

Pourrendreeipluslair,voiilesdénitionsdereturnetbind ainsiqueleurstypesdéveloppés:

(9)

typeαt

valreturn : α → αt

val(>>= ) : αt → (α → β t) → β t

valspawn : (unitunit t) → unit valskip :unit t

valyield : unitunit t

valhalt :unitunit t

valstop :unitunit t

valstart :unitunit

typeαmvar

valmake mvar :unit → αmvar

valput mvar : αmvar → α → unit t

valtake mvar : αmvar → αt

Figure5Signaturesdesprimitivespourlestylemonadique

valreturn : α → (α → unit) → unit

letreturn a =funk → k a

val(>>=) : ((α → unit) → unit) → (α → (β → unit) → unit) → (β → unit) → unit

let(>>=)f k =funk → f (funr → k r k)

'estàdirequebind retourneunefontionquiaepteuneontinuation.Voyonslaformedéveloppée denotreexempletransfer mvar :

transfer mvar m1 m2 , funk → take mvar m1 (funr → (funv → put mvar m2 v)r k)

Enn,spawn fournitlaontinuationvideetplaelafontionenled'exéution.

letspawn (t :unitunit t) = enqueue(fun() → t () (fun() → ()))

Comme on le voit, tout ei est très prohe du style trampoline. Celui-i a été proposé dans le

adredulangageSheme,onpeutvoirlamonadedeontinuationessentiellementommeuneversion

typéedustyletrampoline.

4.5. Monade de promesse

Une promesse [12℄ est une valeurqui peut être utilisée pour aéder plus tardà une valeur qui

n'estpasnéessairementdisponibleimmédiatement.

Une opération qui bloquerait peut retourner immédiatement une promesse.Une promesse peut

être prête si la valeur est disponible ou bloquée si elle ne l'est pas enore. La promesse peut être

simplementpasséejusqu'aupointlavaleurqu'ellereprésenteesteetivementrequise:l'opération

claim permetd'extraireette valeur(ellepeutbienévidemmentbloquer).

Lessignaturesdesopérationsserontlesmêmequepourlamonadedeontinuation(gure5).Les

opérations bloquantes retournentune promesse, leclaim étanteetué parbind. Dans t >>= f,

bind devrasoit:

passeràf lavaleurdet sielleest prête(etretourneralorslapromesseretournéeparf),ou

retournerunepromessebloquéeetfaireensorteque:

f reçoivelavaleurpromisedèsquet devientprête,

lapromessebloquéeretournéesoitlamême queelleretournéeparf.

Réalisation Voii une brève desription d'une réalisation possible (gure 6). Celle-i est très

largement inspirée de Lwt (light weigth threads) [18℄, une bibliothèque OCaml de threads légères.

Nousessayonsden'enreteniriiquelesaspetslesplusfondamentauxpourfailiter laomparaison

avelesautresréalisations.Nousinterprétonsii letypeαt ommeletypedes promessesalorsque

[18℄l'interprèteommeletypedesthreads.

Références

Documents relatifs

Comme α est assez proche de 1, on a également k app nettement inférieur à k, ce qui est conforme au comportement d’un trampoline : enfoncer le centre du trampoline est très

The original type system for shift and reset by Danvy and Filinski [5] is the only type system that allows modification of answer types but is restricted to monomorphic types..

In particular, HTT cc features two type constructors which we use to provide annotations for side-effectful programs and for con- tinuation objects. R describes what holds when

On veut caract´ eriser cette monade par le fait que cette repr´ esentation est initiale, mais dans quelle cat´ egorie ?.. La cat´ egorie des repr´ esentations

Compléter le tableau des caractéristiques du poids ci-dessous puis représenter ce poids sur le schéma avec l'échelle 1 cm pour 2 N. Compléter les deux

D’ici à 2003, évaluer les conséquences écono- miques et sociales de l’épidémie de VIH/sida et établir des stratégies multisectorielles pour: lutter contre les effets

Il permet un gain de temps dans l’apprentissage de certains acrobaties, habitue le gymnaste à tout un ensemble de rotations et développe chez lui la prise de repères dans

Ball out Salto avant, départ plat dos Pull over Rotation arrière, départ dos Barani Salto avant ½ tour Kaboom Plat ventre et salto avant Cody Salto arrière, départ sur