FAQ SDL
FAQ SDLConsultez toutes les FAQ
Nombre d'auteurs : 6, nombre de questions : 67, création le 10 mai 2013
SDL permet une gestion très simple des threads. Pour créer un thread, il suffit de créer une structure SDL_Thread et de l'initialiser avec la fonction SDL_CreateThread, qui prend comme premier paramètre le pointeur de la fonction à exécuter ainsi qu'un éventuel argument à cette fonction dans le second paramètre.
Il faudra toutefois veiller à passer comme paramètre un pointeur sur une fonction retournant un entier (int) et prenant comme paramètre un pointeur vide (void*). Pour plus d'infos, voir https://jeux.developpez.com/faq/sdl/?page=threads#THREADS_fonction_membre.
/* Définition de la fonction qui sera lancée par notre thread */
int
ThreadFunc
(
void
*
Data)
{
/* On convertit le paramètre dans le type réel voulu par la fonction */
SDL_Surface*
Surf =
(
SDL_Surface*
)Data;
/* Remplit la surface en rouge */
SDL_FillRect
(
Surf, NULL
, SDL_MapRGB
(
Surf->
format, 255
, 0
, 0
));
return
0
;
}
/* Déclaration de la structure du thread */
SDL_Thread*
myThread;
/* Création du thread ; dès l'appel de cette fonction, le thread commence à s'exécuter */
myThread =
SDL_CreateThread
(
ThreadFunc, MainScreen);
/* À la fin, on peut attendre sur le thread avant de fermer le programme */
int
result;
SDL_WaitThread
(
myThread, &
result);
printf
(
"
Notre fonction s'est terminée avec le code : %i
"
, result);
À noter que pour utiliser les threads, il faudra inclure l'en-tête « sdl_thread.h » qui n'est pas inclus par défaut dans « sdl.h ».
Lorsque l'on termine le programme, il est conseillé d'attendre que toutes les exécutions de nos threads soient bien terminées pour éviter des erreurs néfastes. Pour cela on peut utiliser la fonction suivante :
void
SDL_WaitThread
(
SDL_Thread*
thread, int
*
status)
Qui est bloquante tant que le thread n'a pas fini son exécution, et qui retourne dans le paramètre status le retour de la fonction que le thread exécutait.
Pour un exemple d'utilisation, voir https://jeux.developpez.com/faq/sdl/?page=threads#THREADS_creer.
Pour forcer l'arrêt d'un thread avant que celui-ci ait fini de s'exécuter, on peut utiliser la fonction suivante, qui stoppera le thread passé en paramètre :
void
SDL_KillThread
(
SDL_Thread*
thread)
Cependant, la destruction d'un thread de cette manière peut être dangereuse, car elle fermera brutalement le thread en cours d'exécution, ce qui peut amener à d'éventuelles erreurs et est déconseillé :
/* Une fonction anodine, mais qui peut cacher des problèmes au sein d'un thread... */
int
ThreadFunc
(
void
*
Data)
{
/* Un tableau de surfaces */
SDL_Surface*
SurfTab[100
];
/* Allocation de 100 surfaces */
for
(
int
i =
0
; i <
100
; i++
)
{
SurfTab[i] =
SDL_CreateRGBSurface
(
SDL_SWSURFACE, 400
, 400
, 32
, 0xff000000
, 0x00ff0000
, 0x0000ff00
, 0x000000ff
);
}
/* Libération des surfaces */
for
(
i =
0
; i <
100
; i++
)
{
SDL_FreeSurface
(
SurfTab[i]);
}
return
0
;
}
La fonction ci-dessus est bien écrite et libère correctement la mémoire juste après en avoir alloué, cependant imaginez que l'on stoppe le thread entre les deux boucles : bonjour les dégâts ! Il est donc recommandé d'attendre la fin normale du thread (voir https://jeux.developpez.com/faq/sdl/?page=threads#THREADS_attente).
Pour éviter que deux ou plusieurs threads ne modifient par exemple une même donnée en même temps, on peut utiliser avec SDL le principe des verrous d'exclusion mutuelle (mutex).
On peut créer avec SDL un mutex avec la fonction :
SDL_mutex*
SDL_CreateMutex
(
)
Qui retourne une structure de type SDL_mutex.
Ensuite, pour bloquer une section de code momentanément aux autres threads, on peut utiliser la fonction
int
SDL_mutexP
(
SDL_mutex*
mutex)
Qui permet de verrouiller le mutex. Si le mutex a déjà été verrouillé par un autre thread, la fonction reste bloquée jusqu'à ce que le mutex ne soit plus verrouillé.
Enfin, lorsque le code à protéger est terminé, on peut débloquer le mutex grâce à la fonction :
int
SDL_mutexV
(
SDL_mutex*
mutex)
À la fin de l'utilisation du mutex, on peut libérer celui-ci avec la fonction :
void
SDL_DestroyMutex
(
SDL_mutex*
mutex)
Voici un petit exemple pratique illustrant tout ceci :
/* Déclaration du mutex */
SDL_mutex*
myMutex =
SDL_CreateMutex
(
);
/* Les deux fonctions suivantes peuvent par exemple travailler sur la surface d'écran,
mais elles ne vont jamais rentrer en conflit, car si l'une des deux a verrouillé le mutex,
l'autre attendra jusqu'à ce que celui-ci soit libre. */
int
ThreadFuncA
(
void
*
Data)
{
SDL_Surface*
MainScreen =
(
SDL_Surface*
) Data
SDL_mutexP
(
myMutex);
/* Traitement sur l'écran... */
SDL_mutexV
(
myMutex);
}
int
ThreadFuncB
(
void
*
Data)
{
SDL_Surface*
MainScreen =
(
SDL_Surface*
) Data
SDL_mutexP
(
myMutex);
/* Un autre traitement sur l'écran... */
SDL_mutexV
(
myMutex);
}
/* Déclaration des deux threads dans le programme */
SDL_Thread*
myFirstThread =
SDL_CreateThread
(
ThreadFuncA, MainScreen);
SDL_Thread*
mySecondThread =
SDL_CreateThread
(
ThreadFuncB, MainScreen);
/* Fin des threads */
SDL_WaitThread
(
myFirstThread, NULL
);
SDL_WaitThread
(
mySecondThread, NULL
);
/* Destruction du mutex */
SDL_DestroyMutex
(
myMutex);
Note : on peut aussi utiliser les macros SDL_LockMutex() et SDL_UnlockMutex() définies par SDL, qui appellent les deux fonctions décrites précédemment. Ces dernières ont l'avantage d'avoir un nom un peu plus explicite…
En C++, beaucoup sont tentés d'utiliser une fonction membre de classe pour lancer un thread. Malheureusement c'est impossible : fonctions membres et fonctions non membres (ou membres statiques) n'ont pas le même type.
SDL_CreateThread attend une fonction de type int(*)(void*).
Une fonction membre équivalente serait de type int(MaClasse::*)(void*).
Il ne faut en effet pas oublier que pour appeler une fonction membre, il faut une instance de la classe.
La solution couramment mise en œuvre pour contourner ce problème est simple : on passe par une fonction statique, mais celle-ci ne fait que rediriger vers la fonction membre équivalente d'une instance que l'on aura passée en paramètre via l'argument void*.
// Notre classe perso pour les threads
class
Thread
{
public
:
// La fonction statique qui sera passée en paramètre à SDL_CreateThread
static
int
GlobalThreadFunc(void
*
Data)
{
// On reconvertit en Thread* le paramètre Data
Thread*
MyThread =
reinterpret_cast
<
Thread*>
(Data);
// Puis on appelle sa véritable fonction de thread
return
MyThread->
ThreadFunc();
}
private
:
// La véritable fonction qui va gérer le thread, qui peut cette fois être membre
int
ThreadFunc()
{
// ...
}
}
;
// On peut lancer un nouveau thread de cette manière
Thread*
NewThread =
new
Thread;
SDL_CreateThread(&
Thread::
GlobalThreadFunc, NewThread);