• Aucun résultat trouvé

Les fonctions d´ecrites dans cette section sont d´efinies dans le module Thread. L’appel syst`eme join permet `a un coprocessus d’en attendre un autre.

val Thread.join : t -> unit

Le coprocessus appelant est alors suspendu jusqu’`a ce que celui dont l’identit´e est pass´ee en argument ait termin´e son ex´ecution. Cet appel peut ´egalement ˆetre utilis´e par le coprocessus principal pour attendre que tous les autres aient retourn´e avant de terminer lui-mˆeme et de terminer le programme (le comportement par d´efaut ´etant de tuer les autres coprocessus sans attendre leur terminaison).

Bien que cet appel soit bloquant donc du type≪long≫, il est relanc´e automatiquement `a la

r´eception d’un signal : il est effectivement interrompu par un signal, le handler est trait´e, puis l’appel est relanc´e. On ne revient donc de l’appel que quand le coprocessus `a r´eellement termin´e et l’appel ne doit jamais lever l’exception EINTR. Du point de vue du programmeur OCaml, cela se comporte comme si le signal ´etait re¸cu au moment o`u l’appel retourne.

Un coprocessus ne retourne pas, car il s’ex´ecute de fa¸con asynchrone. Mais son action peut ˆetre observ´ee — heureusement ! — par ses effets de bords. Par exemple, un coprocessus peut placer le r´esultat d’un calcul dans une r´ef´erence qu’un autre coprocessus ira consulter apr`es s’ˆetre assur´e de la terminaison du calcul. Nous illustrons cela dans l’exemple suivant.

exception Exited

type ’a result = Value of ’a | Exception of exn let eval f x = try Value (f x) with z -> Exception z let coexec (f : ’a -> ’b) (x : ’a) : unit -> ’b =

let result = ref (Exception Exited) in

let p = Thread.create (fun x -> result := eval f x) x in function() ->

match join p; !result with | Value v -> v

| Exception exn -> raise exn;;

let v1 = coexec succ 4 and v2 = coexec succ 5 in v1()+v2();;

Le syst`eme peut suspendre un coprocessus pour donner temporairement la main `a un autre ou parce qu’il est en attente d’une ressource utilis´ee par un de ses coprocessus (verrous et conditions, par exemple) ou par un autre processus (descripteur de fichier, par exemple). Un coprocessus peut ´egalement se suspendre de sa propre initiative. La fonction yield permet `a un coprocessus de redonner la main pr´ematur´ement (sans attendre la pr´eemption par le syst`eme).

val Thread.yield : unit -> unit

C’est une indication pour le gestionnaire de coprocessus, mais son effet peut ˆetre nul, par exemple, si aucun coprocessus ne peut s’ex´ecuter imm´ediatement. Le syst`eme peut donc d´ecider de redonner la main au mˆeme coprocessus.

Inversement, il n’est pas n´ecessaire d’ex´ecuter yield pour permettre `a d’autres coprocessus de s’ex´ecuter, car le syst`eme se r´eserve le droit d’ex´ecuter lui-mˆeme la commande yield `a tout moment. En fait, il exerce ce droit `a intervalles de temps suffisamment rapproch´es pour per- mettre `a d’autres coprocessus de s’ex´ecuter et donner l’illusion `a l’utilisateur que les coprocessus s’ex´ecutent en parall`ele, mˆeme sur une machine mono-processeur.

Exemple: On peut reprendre et modifier l’exemple 3.3 pour utiliser des coprocessus plutˆot que des processus.

1 let rec psearch k cond v = 2 let n = Array.length v in

3 let slice i = Array.sub v (i * k) (min k (n - i * k)) in 4 let slices = Array.init (n/k) slice in

5 let found = ref false in

6 let pcond v = if !found then Thread.exit(); cond v in

7 let search v = if simple_search pcond v then found := true in 8 let proc_list = Array.map (Thread.create search) slices in 9 Array.iter Thread.join proc_list;

10 !found;;

La fonction psearch k f v recherche avec k coprocessus en parall`ele une occurrence dans le tableau satisfaisant la fonction f . La fonction pcond permet d’interrompre la recherche en cours d`es qu’une r´eponse a ´et´e trouv´ee. Tous les coprocessus partagent la mˆeme r´ef´erence found : ils peuvent donc y acc´eder de fa¸con concurrente. Il n’y a pas de section critique entre les diff´erents coprocessus car s’ils ´ecrivent en parall`ele dans cette ressource, ils ´ecrivent la mˆeme valeur. Il est important que les coprocessus n’´ecrivent pas le r´esultat de la recherche quand celui-ci est faux ! par exemple le remplacement de la ligne 7 par

let search v = found := !found && simple_search pcond v ou mˆeme :

let search v = let r = simple_search pcond v in found := !found && r serait incorrect.

la recherche en parall`ele est int´eressante mˆeme sur une machine mono-processeur si la compa- raison des ´el´ements peut ˆetre bloqu´ee temporairement (par exemple par des acc`es disques ou, mieux, des connexions r´eseau). Dans ce cas, le coprocessus qui effectue la recherche passe la main `a un autre et la machine peut donc continuer le calcul sur une autre partie du tableau et revenir au coprocessus bloqu´e lorsque sa ressource sera lib´er´ee.

L’acc`es `a certains ´el´ements peut avoir une latence importante, de l’ordre de la seconde s’il faut r´ecup´erer de l’information sur le r´eseau. Dans ce cas, la diff´erence de comportement entre une recherche s´equentielle et une recherche en parall`ele devient flagrante.

Exercice 18 Parall´eliser le tri rapide (quicksort) sur des tableaux. (Voir le corrig´e) Les autres formes de suspension sont li´ees `a des ressources du syst`eme d’exploitation. Un co- processus peut se suspendre pendant un certain temps en appelant delay s. Une fois s secondes ´ecoul´ees, il pourra ˆetre relanc´e.

val Thread.delay : float -> unit

Cette primitive est fournie pour des raisons de portabilit´e avec les coprocessus simul´es, mais Thread.delay t est simplement une abr´eviation pour ignore (Unix.select [] [] [] t). Cet appel, contrairement `a Thread.join, n’est pas relanc´e lorsqu’il est interrompu par un signal.

Pour synchroniser un coprocessus avec une op´eration externe, on peut utiliser la commande select d’Unix. Bien entendu, celle-ci ne bloquera que le coprocessus appelant et non le pro- gramme tout entier. (Le module Thread red´efinit cette fonction, car dans le cas des coprocessus simul´es, elle ne doit pas appeler directement cette du module Unix, ce qui bloquerait le pro- gramme tout entier, donc tous les coprocessus. Il faut donc utiliser Thread.select et non Unix.select, mˆeme si les deux sont ´equivalents dans le cas des coprocessus natifs.)

Exemple: Pour faire fonctionnner le crible d’´Eratosth`ene avec des coprocessus plutˆot que par duplication de processus Unix, il suffit de remplacer les lignes 30–41 par

let p = Thread.create filter (in_channel_of_descr fd_in) in let output = out_channel_of_descr fd_out in

try

while true do

let n = input_int input in

if List.exists (fun m -> n mod m = 0) first_primes then () else output_int output n

done;

with End_of_file -> close_out output; Thread.join p et les lignes 46–52 par

let k = Thread.create filter (in_channel_of_descr fd_in) in let output = out_channel_of_descr fd_out in

generate len output; close_out output; Thread.join k;;

Toutefois, il ne faut esp´erer aucun gain significatif sur cet exemple qui utilise peu de processus par rapport au temps de calcul.