# Introduction

Ce didacticiel est destiné aux utilisateurs maîtrisant la programmation en C ou C++, ainsi que l'architecture de la Simple Virtual Machine.

Dans ce didacticiel, vous allez apprendre à utiliser les outils de synchronisation offerts par la machine virtuelle.

Le temps de lecture de ce didacticiel est estimé à 35 minutes.

# Mise en place

Pour commencer, créez le canevas de l'extension dans le fichier synchronisation.svm_plugin en utilisant ce code :

    PLUGIN synchro

    DEFINE

# Verrous

Les verrous proposés par l'interface programmatique permettent une gestion plus fine des accès aux ressources que les verrous d'exclusion mutuelle habituels. En effet, ils permettent l'accès à une ressource soit à plusieurs lecteurs soit à un seul écrivain.

## Création de verrou

Modifiez le code de l'extension :

    PLUGIN synchro

    DEFINE

    TYPE synchro.lock
    %{
    	SVM_Lock _lock;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_lock);
    %}

    INSTRUCTION synchro.new_lock -> synchro.lock
    %{
    	auto t = new type_lock;
    	t->_lock = ::svm_lock_new(svm);
    	VARIABLE_GLOBAL(t->_lock);
    	return NEW_PLUGIN(synchro,lock,t);
    %}

La fonction de création de verrou est `svm_lock_new`. Cette fonction ne crée pas de barrière de synchronisation, elle crée juste le verrou.

## Verrouillage en lecture

Le premier verrouillage possible est celui utilisé par une entité qui doit lire la ressource protégée par le verrou.

Modifiez le code de l'extension :

    PLUGIN synchro

    DEFINE

    TYPE synchro.lock
    %{
    	SVM_Lock _lock;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_lock);
    %}

    INSTRUCTION synchro.new_lock -> synchro.lock
    %{
    	auto t = new type_lock;
    	t->_lock = ::svm_lock_new(svm);
    	VARIABLE_GLOBAL(t->_lock);
    	return NEW_PLUGIN(synchro,lock,t);
    %}

    TYPE synchro.read
    %{
    	SVM_LockGuard_Read _read;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_read);
    %}

    WAITING INSTRUCTION synchro.lock_read synchro.lock -> synchro.read
    %{
    	auto l = ARGV_PLUGIN(0,synchro,lock);
    	auto t = new type_read;
    	t->_read = ::svm_lock_readguard_new(svm,l->_lock,TRUE);
    	VARIABLE_GLOBAL(t->_read);
    	return NEW_PLUGIN(synchro,read,t);
    %}

La fonction `svm_lock_readguard_new` cette fois crée une barrière de synchronisation pour un lecteur. Une fois la variable Simple Virtual Machine de type `SVM_LockGuard_Read` obtenue, la barrière de synchronisation est active jusqu'à ce que cette variable soit détruite.

## Verrouillage en écriture

De la même manière, un verrouillage pour un accès en écriture peut être obtenu depuis le verrou.

Modifiez le code de l'extension :

    PLUGIN synchro

    DEFINE

    TYPE synchro.lock
    %{
    	SVM_Lock _lock;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_lock);
    %}

    INSTRUCTION synchro.new_lock -> synchro.lock
    %{
    	auto t = new type_lock;
    	t->_lock = ::svm_lock_new(svm);
    	VARIABLE_GLOBAL(t->_lock);
    	return NEW_PLUGIN(synchro,lock,t);
    %}

    TYPE synchro.read
    %{
    	SVM_LockGuard_Read _read;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_read);
    %}

    WAITING INSTRUCTION synchro.lock_read synchro.lock -> synchro.read
    %{
    	auto l = ARGV_PLUGIN(0,synchro,lock);
    	auto t = new type_read;
    	t->_read = ::svm_lock_readguard_new(svm,l->_lock,TRUE);
    	VARIABLE_GLOBAL(t->_read);
    	return NEW_PLUGIN(synchro,read,t);
    %}

    TYPE synchro.write
    %{
    	SVM_LockGuard_Write _write;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_write);
    %}

    WAITING INSTRUCTION synchro.lock_write synchro.lock -> synchro.write
    %{
    	auto l = ARGV_PLUGIN(0,synchro,lock);
    	auto t = new type_write;
    	t->_write = ::svm_lock_writeguard_new(svm,l->_lock,TRUE);
    	VARIABLE_GLOBAL(t->_write);
    	return NEW_PLUGIN(synchro,write,t);
    %}

Cette fois, c'est la fonction `svm_lock_writeguard_new` qui crée une barrière de synchronisation plus forte que celle de la lecture : tous les écrivains sont exclusifs !

Générez et compilez l'extension, puis créez un fichier exécutable avec ce code de test :

    #!/usr/bin/env svm
    LOG
    LOCAL PLUGIN "svmpluginsynchro/libsvmsynchro.so"
    PLUGIN "svmrun.so"
    PLUGIN "svmint.so"
    PROCESS "main"
    	CODE "main" INLINE
    		:memory (synchro.lock,STR)/p, synchro.write/w, INT/i
    		:synchro.new_lock -> &p
    		:synchro.lock_write @&p -> &w
    		0 -> &i
    	:label create
    		:int.print @&i -> (p/1)
    		:run.parallel_call $"lock" p @(p/1) SCHED=run.parallel PARAMS=SHARE
    		:shift &i
    		:goto create :when @&i IN &0*5
    		:shutdown
    	:symbol lock
    		:memory synchro.read/r, synchro.write/w, STR/s
    		@(P/1) -> &s
    		:synchro.lock_write @&P -> &w
    		@&s -> (P/1)
    		:run.trace @(P/1)
    		:run.sleep HARD 2
    		[ ] -> w
    		:synchro.lock_read @&P -> &r
    		:run.trace @(P/1)
    		:run.sleep HARD 2
    		[ ] -> r
    		:shutdown
    	END
    END

Lorsque vous lancez cette application, vous devriez voir chaque processus écrire la valeur tour à tour avec des valeurs différentes, puis tous les processus écrirent la dernière valeur écrite en même temps : les écritures sont séquencielles et les lectures sont parallèles.

- Les verrous sont un outil basique de synchronisation, souvent utiles pour protéger des sections de code dans des processus différents.
- La fonction d'interface programmatique `svm_lock_new` permet de créer un verrou.
- La fonction d'interface programmatique `svm_lock_readguard_new` permet de verrouiller pour une lecture : les autres verrouillages en lecture ne seront pas bloqués, mais les verrouillages en écriture seront quant à eux bloqués.
- La fonction d'interface programmatique `svm_lock_writeguard_new` permet de verrouiller pour une écriture : les autres verrouillages en lecture et en écriture seront bloqués.

# Evénements

Les verrous deviennent rapidement limités lorsque les processus à synchroniser deviennent nombreux, et qu'ils doivent échanger des informations en plus de la synchronisation.

Pour pallier à ce défaut, l'interface programmatique propose un autre mécanisme de synchronisation beaucoup plus évolué : la synchronisation sur événements.

## Adresses

Dans le système d'événements, ce sont des adresses qui émettent ou réceptionnent les événements proprement dits.

Modifiez le code de l'extension :

    PLUGIN synchro

    DEFINE

    TYPE synchro.address
    %{
    	SVM_Event_Queue_Address _address;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_address);
    %}
    copy object:
    %{
    	auto c = new type_address;
    	c->_address = object->_address;
    	VARIABLE_GLOBAL(c->_address);
    	return c;
    %}

    STRUCT synchro.address
    %{
    	SVM_Value _value;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_value);
    %}

    INSTRUCTION synchro.new_address VALUE ? -> synchro.address
    %{
    	auto a = new type_address;
    	if(argc==0)
    	{
    		a->_address = ::svm_event_address_new(svm);
    	}
    	else
    	{
    		SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
    		VARIABLE_GLOBAL(v);
    		auto s = new struct_address;
    		s->_value = v;
    		a->_address = ::svm_event_address_new_struct(svm,NEW_STRUCT(synchro,address,s));
    	}
    	::svm_variable_scope_set_shared(svm,a->_address);
    	VARIABLE_GLOBAL(a->_address);
    	return NEW_PLUGIN(synchro,address,a);
    %}

Ici, deux types d'adresses peuvent être créées :

1. les adresses simples sans structure,
2. les adresses avec structure, qui permettent d'identifier grâce au contenu de la structure qui est l'émetteur ou le récepteur d'un événement.

Une fois l'adresse créée, il est possible de déterminer si une adresse possède une structure avec la fonction `svm_event_address_has_struct` et le cas échéant, de récupérer cette structure grâce à la fonction `svm_event_address_get_struct`.

Notez également que le type contient l'adresse sous forme de variable Simple Virtual Machine partagée, pour permettre la copie du type contenant l'adresse.

## Files d'événements

Il est maintenant temps de s'intéresser au second concept important des événements : les files d'événements. Ce sont elles qui transmettent les événements entre adresses, dans la mesure où ces adresses sont rattachées à la file d'événements.

### Création

En premier lieu, il faut créer les files d'événements.

Modifiez le code de l'extension :

    PLUGIN synchro

    DEFINE

    TYPE synchro.address
    %{
    	SVM_Event_Queue_Address _address;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_address);
    %}
    copy object:
    %{
    	auto c = new type_address;
    	c->_address = object->_address;
    	VARIABLE_GLOBAL(c->_address);
    	return c;
    %}

    STRUCT synchro.address
    %{
    	SVM_Value _value;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_value);
    %}

    INSTRUCTION synchro.new_address VALUE ? -> synchro.address
    %{
    	auto a = new type_address;
    	if(argc==0)
    	{
    		a->_address = ::svm_event_address_new(svm);
    	}
    	else
    	{
    		SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
    		VARIABLE_GLOBAL(v);
    		auto s = new struct_address;
    		s->_value = v;
    		a->_address = ::svm_event_address_new_struct(svm,NEW_STRUCT(synchro,address,s));
    	}
    	::svm_variable_scope_set_shared(svm,a->_address);
    	VARIABLE_GLOBAL(a->_address);
    	return NEW_PLUGIN(synchro,address,a);
    %}

    TYPE synchro.queue
    %{
    	SVM_Event_Queue _queue;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_queue);
    %}
    copy object:
    %{
    	auto c = new type_queue;
    	c->_queue = object->_queue;
    	VARIABLE_GLOBAL(c->_queue);
    	return c;
    %}

    INSTRUCTION synchro.new_queue -> synchro.queue
    %{
    	auto t = new type_queue;
    	t->_queue = ::svm_event_queue_new(svm);
    	VARIABLE_GLOBAL(t->_queue);
    	::svm_variable_scope_set_shared(svm,t->_queue);
    	return NEW_PLUGIN(synchro,queue,t);
    %}

Lorsqu'une file d'événements est créée, aucune adresse n'est rattachée à la file.

### Rejoindre et quitter

Pour qu'une adresse soit connue d'une file d'événements, il faut que l'adresse rejoigne la file d'événements.

Modifiez le code de l'extension :

    PLUGIN synchro

    DEFINE

    TYPE synchro.address
    %{
    	SVM_Event_Queue_Address _address;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_address);
    %}
    copy object:
    %{
    	auto c = new type_address;
    	c->_address = object->_address;
    	VARIABLE_GLOBAL(c->_address);
    	return c;
    %}

    STRUCT synchro.address
    %{
    	SVM_Value _value;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_value);
    %}

    INSTRUCTION synchro.new_address VALUE ? -> synchro.address
    %{
    	auto a = new type_address;
    	if(argc==0)
    	{
    		a->_address = ::svm_event_address_new(svm);
    	}
    	else
    	{
    		SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
    		VARIABLE_GLOBAL(v);
    		auto s = new struct_address;
    		s->_value = v;
    		a->_address = ::svm_event_address_new_struct(svm,NEW_STRUCT(synchro,address,s));
    	}
    	::svm_variable_scope_set_shared(svm,a->_address);
    	VARIABLE_GLOBAL(a->_address);
    	return NEW_PLUGIN(synchro,address,a);
    %}

    TYPE synchro.queue
    %{
    	SVM_Event_Queue _queue;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_queue);
    %}
    copy object:
    %{
    	auto c = new type_queue;
    	c->_queue = object->_queue;
    	VARIABLE_GLOBAL(c->_queue);
    	return c;
    %}

    INSTRUCTION synchro.new_queue -> synchro.queue
    %{
    	auto t = new type_queue;
    	t->_queue = ::svm_event_queue_new(svm);
    	VARIABLE_GLOBAL(t->_queue);
    	::svm_variable_scope_set_shared(svm,t->_queue);
    	return NEW_PLUGIN(synchro,queue,t);
    %}

    INSTRUCTION synchro.join synchro.queue <= synchro.address
    %{
    	auto q = ARGV_PLUGIN(0,synchro,queue);
    	auto a = ARGV_PLUGIN(2,synchro,address);
    	::svm_event_queue_join(svm,q->_queue,a->_address);
    %}

    INSTRUCTION synchro.leave synchro.queue => synchro.address
    %{
    	auto q = ARGV_PLUGIN(0,synchro,queue);
    	auto a = ARGV_PLUGIN(2,synchro,address);
    	::svm_event_queue_leave(svm,q->_queue,a->_address);
    %}

### Echanger des événements

Il ne reste plus qu'à envoyer et recevoir les événements.

Modifiez le code de l'extension :

    PLUGIN synchro

    DEFINE

    TYPE synchro.address
    %{
    	SVM_Event_Queue_Address _address;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_address);
    %}
    copy object:
    %{
    	auto c = new type_address;
    	c->_address = object->_address;
    	VARIABLE_GLOBAL(c->_address);
    	return c;
    %}

    STRUCT synchro.address
    %{
    	SVM_Value _value;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_value);
    %}

    INSTRUCTION synchro.new_address VALUE ? -> synchro.address
    %{
    	auto a = new type_address;
    	if(argc==0)
    	{
    		a->_address = ::svm_event_address_new(svm);
    	}
    	else
    	{
    		SVM_Value v = ::svm_parameter_value_get(svm,argv[1]);
    		VARIABLE_GLOBAL(v);
    		auto s = new struct_address;
    		s->_value = v;
    		a->_address = ::svm_event_address_new_struct(svm,NEW_STRUCT(synchro,address,s));
    	}
    	::svm_variable_scope_set_shared(svm,a->_address);
    	VARIABLE_GLOBAL(a->_address);
    	return NEW_PLUGIN(synchro,address,a);
    %}

    TYPE synchro.queue
    %{
    	SVM_Event_Queue _queue;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_queue);
    %}
    copy object:
    %{
    	auto c = new type_queue;
    	c->_queue = object->_queue;
    	VARIABLE_GLOBAL(c->_queue);
    	return c;
    %}

    INSTRUCTION synchro.new_queue -> synchro.queue
    %{
    	auto t = new type_queue;
    	t->_queue = ::svm_event_queue_new(svm);
    	VARIABLE_GLOBAL(t->_queue);
    	::svm_variable_scope_set_shared(svm,t->_queue);
    	return NEW_PLUGIN(synchro,queue,t);
    %}

    INSTRUCTION synchro.join synchro.queue <= synchro.address
    %{
    	auto q = ARGV_PLUGIN(0,synchro,queue);
    	auto a = ARGV_PLUGIN(2,synchro,address);
    	::svm_event_queue_join(svm,q->_queue,a->_address);
    %}

    INSTRUCTION synchro.leave synchro.queue => synchro.address
    %{
    	auto q = ARGV_PLUGIN(0,synchro,queue);
    	auto a = ARGV_PLUGIN(2,synchro,address);
    	::svm_event_queue_leave(svm,q->_queue,a->_address);
    %}

    STRUCT synchro.event
    %{
    	SVM_Value _value;
    %}
    delete default:
    %{
    	VARIABLE_LOCAL(object->_value);
    %}

    INSTRUCTION synchro.push synchro.queue { synchro.address => [ synchro.address 'ALL' ] } VALUE
    %{
    	auto q = ARGV_PLUGIN(0,synchro,queue);
    	auto s = ARGV_PLUGIN(2,synchro,address);
    	type_address *d = nullptr;
    	if(::svm_parameter_type_is_value(svm,argv[4]))
    	{
    		d = ARGV_PLUGIN(4,synchro,address);
    	}
    	SVM_Value v = ::svm_parameter_value_get(svm,argv[6]);
    	auto e = new struct_event;
    	e->_value = v;
    	VARIABLE_GLOBAL(e->_value);
    	if(d)
    	{
    		::svm_event_queue_push(svm,q->_queue,d->_address,s->_address,NEW_STRUCT(synchro,event,e));
    	}
    	else
    	{
    		::svm_event_queue_broadcast(svm,q->_queue,s->_address,NEW_STRUCT(synchro,event,e));
    	}
    %}

    WAITING INSTRUCTION synchro.pull synchro.queue { 'ANY' => synchro.address } INT ? -> PTR ?
    %{
    	auto q = ARGV_PLUGIN(0,synchro,queue);
    	auto d = ARGV_PLUGIN(3,synchro,address);
    	SVM_Value_Integer t = nullptr;
    	if(argc>6)
    	{
    		t = ::svm_parameter_value_get(svm,argv[5]);
    	}
    	SVM_Event_Queue_Address s = nullptr;
    	SVM_Structure e = nullptr;
    	SVM_Boolean b = TRUE;
    	::svm_process_interruptionnotification_enable(svm,CURRENT(process));
    	if(t)
    	{
    		b = ::svm_event_queue_check(svm,q->_queue,d->_address,&s,&e,t,TRUE);
    	}
    	else
    	{
    		b = ::svm_event_queue_pull(svm,q->_queue,d->_address,&s,&e,TRUE);
    	}
    	::svm_process_interruptionnotification_disable(svm,CURRENT(process));
    	if(b==FALSE)
    	{
    		return NEW_NULL_VALUE(pointer);
    	}
    	SVM_Value *tv = ::svm_value_array_new(svm,2);
    	VARIABLE_GLOBAL(s);
    	auto ra = new type_address { s };
    	tv[0] = NEW_PLUGIN(synchro,address,ra);
    	::svm_value_state_set_movable(svm,tv[0]);
    	auto ee = reinterpret_cast<struct_event*>(::svm_structure_get_internal(svm,CONST_PEP(synchro,event),e));
    	tv[1] = ::svm_value_copy(svm,ee->_value);
    	::svm_value_state_set_movable(svm,tv[1]);
    	SVM_Memory_Zone z = ::svm_memory_zone_new(svm);
    	::svm_memory_zone_append_external__raw(svm,z,CONST_PEP(synchro,address),1);
    	::svm_memory_zone_append_internal__raw(svm,z,::svm_value_type_get_internal(svm,tv[1]),1);
    	SVM_Value_Pointer p = ::svm_memory_allocate(svm,CURRENT(kernel),z);
    	::svm_memory_write_pointer(svm,CURRENT(kernel),p,tv);
    	return p;
    %}

Cette fois, le code est moins simple à comprendre ! Prenons le temps de décrire ces deux nouvelles instructions :

- L'instruction `INSTRUCTION synchro.push synchro.queue { synchro.address => [ synchro.address 'ALL' ] } VALUE` envoie une valeur sous forme d'événement dans la file d'événements :
  - les éléments entre accolades indiquent les adresses source et destination de l'événement,
  - lorsque la destination est une vraie adresse, l'événement est envoyé à cette adresse (grâce à `svm_event_queue_push`),
  - lorsque la destination est le mot-clef `ALL`, l'événement est envoyé à toutes les adresses rattachées à la file d'événements (grâce à `svm_event_queue_broadcast`),
  - un événement est implémenté par une structure d'extension et peut donc contenir tout ce que l'implémenteur de l'extension désire,
  - la structure qui représente l'événement n'a pas besoin d'être rendue globale car la file d'événements conservera cette structure jusqu'à sa reception.
- L'instruction `WAITING INSTRUCTION synchro.pull synchro.queue { 'ANY' => synchro.address } INT ? -> PTR ?` récupère une valeur sous forme d'événement depuis la file d'événements :
  - les éléments entre accolades indiquent les adresses source et destination de l'événement,
  - cette fois, la source est toujours le mot-clef `ANY`, mais un entier peut être ajouté à la fin des paramètres pour indiquer la durée maximale d'attente en millisecondes (en utilisant `svm_event_queue_check` au lieu de `svm_event_queue_pull` qui elle attend indéfinimment),
  - les fonctions d'attente d'événement sont placées entre les fonctions d'activation et de désactivation des notifications d'interruption : elles peuvent être interrompues par des interruptions de la machine virtuelle, ce qui rend le processus bien plus réactif aux ordres de suspension et de terminaison,
  - quand la fonction qui attend l'événement a été interrompue, elle renvoie `FALSE` : ici, l'instruction renvoie un pointeur nul,
  - sinon, l'adresse source et la valeur contenue dans l'événement sont écrites en mémoire (ce qui peut être utile pour retourner une réponse à l'envoyeur) et le pointeur vers ces deux valeurs est retourné,
  - la valeur retournée est ici copiée, car la réutiliser telle qu'elle peut engendrer des problèmes de partage de valeur sans synchronisation,
  - l'instruction est classée `WAITING` à cause des fonctions d'attente d'événement.

Générez et compilez l'extension, puis créez un fichier exécutable avec ce code de test :

    #!/usr/bin/env svm
    LOG
    LOCAL PLUGIN "svmpluginsynchro/libsvmsynchro.so"
    PLUGIN "svmrun.so"
    PLUGIN "svmint.so"
    PLUGIN "svmstr.so"
    PROCESS "main"
    	CODE "main" INLINE
    		:memory INT/i, STR/s, (INT, synchro.queue, synchro.address*5)/a
    		:synchro.new_queue -> (a/1)
    		2 -> &i
    	:label address
    		@&i -> &a
    		:shift -2 &a
    		:synchro.new_address @&a -> (a/@&i)
    		:synchro.join @(a/1) <= @(a/@&i)
    		:shift &i
    		:goto address :when @&i IN a
    		2 -> &i
    	:label process
    		@&i -> &a
    		:shift -2 &a
    		:int.print @&a -> &s
    		:run.parallel_call $"event" a @&s SCHED=run.parallel PARAMS=COPY
    		:shift &i
    		:goto process :when @&i IN a
    		:shutdown
    	:symbol event
    		:memory INT/n, INT/p, PTR/r, STR/e
    		SIZE P -> &p
    		:shift -2 &p
    		@&P -> &n
    		:shift &n
    		:int.mod @&n @&p -> &n
    		:int.print @&P -> &e
    		:str.join "event_" @&e -> &e
    		:synchro.push @(P/1) { @((P/@&P)+2) => @((P/@&n)+2) } @&e
    		:synchro.pull @(P/1) { ANY => @((P/@&P)+2) } -> &r
    		:shutdown :unless &r INITIALISED
    		:run.trace @(@&r/1)
    		:synchro.leave @(P/1) => @((P/@&P)+2)
    		:shutdown
    	END
    END

Lancez cette application, et remarquez dans le résultat que chaque processus récupère une donnée forgée dans un autre processus : les files d'événements sont un outil très pratique pour réaliser simplement une communication inter-processus fiable et opérable.

- Une file d'événements est une structure qui permet à plusieurs processus de s'échanger des informations de manière synchronisées.
- Une file d'événements est créée avec la fonction `svm_event_queue_new`.
- Une adresse est rattachée ou enlevée d'une file d'événements avec les fonctions `svm_event_queue_join` et `svm_event_queue_leave`.
- Un événement est envoyé avec les fonctions `svm_event_queue_push` (pour un envoi à une seule autre adresse de la file) ou `svm_event_queue_broadcast` (pour un envoi à toutes les adresses de la file).
- Un événement est reçu avec les fonctions `svm_event_queue_pull` (pour une attente de durée indéterminée) ou `svm_event_queue_check` (pour une attente à durée limitée).
- Les files d'événements permettent de garder une application parfaitement opérable, car les attentes d'événements peuvent être interrompues pour réagir à des interruptions de la machine virtuelle.

# Conclusion

Vous venez de voir comment utiliser les outils de synchronisation depuis une extension.

L'usage des verrous est très orienté protection locale de donnée partagée et se maîtrise rapidement.

Celui des files d'événements est plus long à prendre en main, mais devient cruxial au sein d'une application impliquant plusieurs processus de traitement devant communiquer entre eux.

Dans les deux cas, les alternatives du système d'exploitation sont possibles, mais parfois au prix de la réactivité de l'application aux notifications processus.