Outils pour utilisateurs

Outils du site


allegro:threads

**Ceci est une ancienne révision du document !** ----

A PCRE internal error occured. This might be caused by a faulty plugin

===== Threads ===== Dans cet article nous allons voir comment utiliser l'interface de threading d'Allegro. ==== Basic Example ==== Danc cet exemple nous utiliserons le même bitmap que dans l'exemple précédent, mais au lieun de changer les axes avec les souris, nous allons utiliser des threads pour le faire.\\ Définissons : Thread Parent: Tout ce qui est dans notre fonction **int main()**. Premier Thread: Le premier Thread crée. Second Thread: Le second thread crée. <file c main.c> #include <stdio.h> #include <allegro5/allegro.h> typedef struct s_data { ALLEGRO_MUTEX *mutex; ALLEGRO_COND *cond; float posiX; float posiY; bool modi_X; bool ready; } DATA; void init_DATA(DATA *data) { data->mutex = al_create_mutex(); data->cond = al_create_cond(); data->posiX = 0; data->posiY = 0; data->modi_X = false; data->ready = false; } void dest_DATA(DATA *data) { al_destroy_mutex(data->mutex); al_destroy_cond(data->cond); } const float FPS = 30; const int SCREEN_W = 640; const int SCREEN_H = 480; const int BOUNCER_SIZE = 32; static void *Func_Thread(ALLEGRO_THREAD *thr, void *arg); int main(int argc, char **argv) { float X, Y; ALLEGRO_DISPLAY *display = NULL; ALLEGRO_EVENT_QUEUE *event_queue = NULL; ALLEGRO_TIMER *timer = NULL; ALLEGRO_BITMAP *bouncer = NULL; ALLEGRO_THREAD *thread_1 = NULL; ALLEGRO_THREAD *thread_2 = NULL; DATA data; bool redraw = true; if(!al_init()) { fprintf(stderr, "failed to initialize allegro!\n"); return -1; } if(!al_install_mouse()) { fprintf(stderr, "failed to initialize the mouse!\n"); return -1; } timer = al_create_timer(1.0 / FPS); if(!timer) { fprintf(stderr, "failed to create timer!\n"); return -1; } display = al_create_display(SCREEN_W, SCREEN_H); if(!display) { fprintf(stderr, "failed to create display!\n"); al_destroy_timer(timer); return -1; } bouncer = al_create_bitmap(BOUNCER_SIZE, BOUNCER_SIZE); if(!bouncer) { fprintf(stderr, "failed to create bouncer bitmap!\n"); al_destroy_display(display); al_destroy_timer(timer); return -1; } al_set_target_bitmap(bouncer); al_clear_to_color(al_map_rgb(255, 0, 255)); al_set_target_bitmap(al_get_backbuffer(display)); event_queue = al_create_event_queue(); if(!event_queue) { fprintf(stderr, "failed to create event_queue!\n"); al_destroy_bitmap(bouncer); al_destroy_display(display); al_destroy_timer(timer); return -1; } al_register_event_source(event_queue, al_get_display_event_source(display)); al_register_event_source(event_queue, al_get_timer_event_source(timer)); al_register_event_source(event_queue, al_get_mouse_event_source()); al_clear_to_color(al_map_rgb(0,0,0)); al_flip_display(); al_start_timer(timer); init_DATA(&data); thread_1 = al_create_thread(Func_Thread, &data); al_start_thread(thread_1); al_lock_mutex(data.mutex); while (!data.ready){ al_wait_cond(data.cond, data.mutex); } al_unlock_mutex(data.mutex); al_lock_mutex(data.mutex); data.modi_X = true; data.ready = false; al_unlock_mutex(data.mutex); thread_2 = al_create_thread(Func_Thread, &data); al_start_thread(thread_2); al_lock_mutex(data.mutex); while (!data.ready){ al_wait_cond(data.cond, data.mutex); } al_unlock_mutex(data.mutex); while(1) { ALLEGRO_EVENT ev; al_wait_for_event(event_queue, &ev); if(ev.type == ALLEGRO_EVENT_TIMER) { redraw = true; } else if(ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { break; } else if(ev.type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN) { break; } if(redraw && al_is_event_queue_empty(event_queue)) { redraw = false; al_lock_mutex(data.mutex); X = data.posiX; Y = data.posiY; al_unlock_mutex(data.mutex); al_draw_bitmap(bouncer, X, Y, 0); al_flip_display(); } } al_destroy_thread(thread_1); al_destroy_thread(thread_2); dest_DATA(&data); al_destroy_bitmap(bouncer); al_destroy_timer(timer); al_destroy_display(display); al_destroy_event_queue(event_queue); return 0; } static void *Func_Thread(ALLEGRO_THREAD *thr, void *arg) { DATA *data = (DATA*) arg; float num = 0.1; al_lock_mutex(data->mutex); data->ready = true; al_broadcast_cond(data->cond); al_unlock_mutex(data->mutex); while(!al_get_thread_should_stop(thr)){ al_lock_mutex(data->mutex); if(data->modi_X) data->posiX += num; else data->posiY += num; al_unlock_mutex(data->mutex); al_rest(0.01); } return NULL; } </file> ==== Détails ==== <code c> typedef struct s_data { ALLEGRO_MUTEX *mutex; ALLEGRO_COND *cond; float posiX; float posiY; bool modi_X; bool ready; } DATA; </code> On crée une structure qui contient les données partagées entre les différents threads.\\ //Note: Vous pouvez déclarer une variable globale de type DATA dans vos programmes.// <code c> void init_DATA(DATA *data) { data->mutex = al_create_mutex(); data->cond = al_create_cond(); data->posiX = 0; data->posiY = 0; data->modi_X = false; data->ready = false; } void dest_DATA(DATA *data) { al_destroy_mutex(data->mutex); al_destroy_cond(data->cond); } </code> Ces fonctions initialisent et libèrent le contenu de notre structure déclarée plus tôt. <code c>ALLEGRO_MUTEX *mutex</code> Nous déclarons un mutex (mutual exclusion). Il s'agit d'un drapeau pour dire aux autres threads de libérer les données. Vous ne pouvez pas modifier sa valeur directement, vous allez comprendre pourquoi. <code c>ALLEGRO_COND *cond;</code> Nous déclarons une condition. C'est une sorte de drapeau qui peut être accédé de manière asynchrone par les autres threads. Encore une fois vous devrez passer par des fonctions pour y accéder. <code c> float posiX; float posiY; bool modi_X; bool ready;</code> **posiX**, et **posiY** sont les coordonnées X et Y de notre bitmap, **modi_X** et **ready** sont deux flags dont nous allons avoir besoin. <code c> ALLEGRO_THREAD *thread_1 = NULL; ALLEGRO_THREAD *thread_2 = NULL; </code> Ensuite dans notre fonction **int main()** on crée un pointeur de type **ALLEGRO_THREAD**, structure qui représente un thread. Un thread étant comme un programme tournant à part. <code c>DATA data;</code> On crée une variable de type DATA seulement maintenant, et pas de portée globale, vous allez voir pourquoi. <code c>thread_1 = al_create_thread(Func_Thread, &data); al_start_thread(thread_1);</code> On crée notre thread et cette fonction permet de transmettre un pointeur en paramètre, pratique pour nos données sans utiliser une variable de portée globale. **Func_Thread** est un pointeur vers une fonction. à sa création, le thread est à l’arrêt. Nous devons appeler **al_start_thread(thread_1)** pour lancer son exécution. Ainsi vous devez écrire votre propre fonction qui doit avoir ce prototype : <code c> static void *Func_Thread(ALLEGRO_THREAD *thr, void *arg) </code> Quel que soit le contenu de cette fonction il sera exécuté dans un thread différent. La suite est plus technique : <code c> al_lock_mutex(data.mutex); while (!data.ready){ al_wait_cond(data.cond, data.mutex); } al_unlock_mutex(data.mutex); </code> Tous d'abord on **lock** le mutex, c'est notre manière de prévenir Allegro qu'un de nos threads va utiliser une ressource partagée.\\ C'est comparable au cri "//C'est mon tour maintenant !//" le premier qui cri obtient le mutex.\\ Si un autre thread a le mutex, alors nous devons attendre que celui-ci l'**unlock**.\\ **while (!data.ready){** On commence par vérifier que le drapeau **ready** est à **true**, car si c'est le cas, on a pas besoin d'attendre la condition, en effet ça signifie que le second thread crée précédemment s'est exécuté plus rapidement que son thread parent et qu'il est prêt.\\ Si **ready** n'est pas à **true**, **al_wait_cond** va unlock le mutex précédemment locké et interrompra ce thread tant que la condition n'est pas vérifiée.\\ Une fois que **al_wait_cond** retourne, elle nous obtient le mutex donc il ne faudra pas oublier de l'unlock avec **al_unlock_mutex(data.mutex)**. **al_wait_cond** can return for other reasons other than the condition becoming true (e.g. the process was signalled). If multiple threads are blocked on the condition variable, the condition may no longer be true by the time the second and later threads are unblocked. Remember not to unlock the mutex prematurely. Maintenant vous devriez comprendre l'intérêt du **while** autour de **al_wait_cond**; et la présence de la variable **ready** dans notre structure **DATA**. <code c> al_lock_mutex(data.mutex); data.modi_X = true; data.ready = false; al_unlock_mutex(data.mutex); thread_2 = al_create_thread(Func_Thread, &data); al_start_thread(thread_2); al_lock_mutex(data.mutex); while (!data.ready){ al_wait_cond(data.cond, data.mutex); } al_unlock_mutex(data.mutex); </code> Quand on arive à ce point du programme c'est que nous en avons fini avec notre premier thread : nous allons maintenant créer et démarrer notre second thread.\\ On commence par locker le mutex, on met le drapeau **modi_X** sur true, When we come to this point its because our first thread is already done. Now we're going to create and start our second thread, so first we lock the mutex, and change the flag **modi_X** to true, the first thread received this flag as false so the first thread will be changing the Y axis of the bitmap, and the second thread, since it will receive it as true, will change the X axis. We also set our ready flag to false again since we need to do the same process we did with the first thread. After that, we proceed to create our second thread, as you can see we're using the same function (**Func_Thread**), for that reason we changed the flag **modi_X** to true otherwise we could create another thread function which changes the X value, but we would need to rewrite all the code again just to change a little bit of the code and that is not a good practice. After starting the second thread we need wait until is done, so we do the same we did with the first one. Now before we move on, let's see what it's doing the **Func_Thread** function. <code c> static void *Func_Thread(ALLEGRO_THREAD *thr, void *arg){ DATA *data = (DATA*) arg; float num = 0.1; al_lock_mutex(data->mutex); bool modi_X = data->modi_X; data->ready = true; al_broadcast_cond(data->cond); al_unlock_mutex(data->mutex); while(!al_get_thread_should_stop(thr)){ al_lock_mutex(data->mutex); if(modi_X) data->posiX += num; else data->posiY += num; al_unlock_mutex(data->mutex); al_rest(0.01); } return NULL; } </code> The data you sent using **al_create_thread()** is received by this function as a void pointer data type **(void *arg)**. So the first thing we need to do inside here is convert our object back to a DATA datatype, which is our original class. <code c> DATA *data = (DATA*) arg; </code> With some typecasting it's done. Remember that data now it's a pointer to a DATA object so we'll use **data->example** instead of **data.example**. <code c> float num = 0.1; </code> This is the value we're going to add to our X or Y axis. <code c> al_lock_mutex(data->mutex); </code> Again, we lock the mutex since we're going to use shared resources. <code c> bool modi_X = data->modi_X; </code> So, here we're creating a bool variable , and assigning it a value. As you can see, when we created our DATA object, this value is initialized as false, so the <code>bool modi_X</code> variable of this thread, if it's the first thread created, is going to be false. We're doing this because we need to tell the first thread to modified the X value and the second thread to modified the Y value, so the thread which receive **modi_X** (Modify X) as true, is going to do exactly that, modified the X Axi, and the thread which receive **modi_X** as false, is going to modified the Y axis. <code c> data->ready = true; </code> This is the second flag of our data object, we need to use this, because our parent thread needs to wait until we have assigned the **modi_X** value. Since we're working with threads we can never assume the order or speed in which two things in parallel happen. So this flag along with the cond struct, allow us to tell our parent thread, when it can move on. <code c>al_broadcast_cond(data->cond); </code> After assigning the threads **modi_X** to false, we change the ready flag to true, to tell to the parent thread that it doesn't need to wait, because the thread is ready, but in case it's waiting, it'll be waiting for this cond, so we also broadcast our condition so it can move on, with this line: <code c> al_unlock_mutex(data->mutex); </code> Finally we unlock our mutex to tell Allegro that we're not going to be using the data object anymore so other threads can start using it. Ok, we need to go back to our parent thread, and make a little review: <code c> thread_1 = al_create_thread(Func_Thread, &data); al_start_thread(thread_1); al_lock_mutex(data.mutex); if (!data.ready) al_wait_cond(data.cond, data.mutex); al_unlock_mutex(data.mutex); al_lock_mutex(data.mutex); data.modi_X = true; data.ready = false; al_unlock_mutex(data.mutex); thread_2 = al_create_thread(Func_Thread, &data); al_start_thread(thread_2); al_lock_mutex(data.mutex); if (!data.ready) al_wait_cond(data.cond, data.mutex); al_unlock_mutex(data.mutex); </code> === Quick Review === * In line 1 we create the thread. * In line 2 we start the thread. * in line 4 we lock our mutex because we're going to check if **ready** it's true * In case it's true that means our second tread for some reason haven't initialized its **mosi_X** bool variable, so we need to wait. * In line 6 we start waiting. * Ok our first thread has broadcast the conditional that means he is ready. * In line 8 we unlock the mutex because we're not going to use it anymore. * In line 10 we lock the mutex again, we need to change our variables because we're about to create another thread, and if we don't change the flags the second thread would be a complete mess. * In line 11 we change our **modi_X** variable to true, that way our second thread it's going to modified the X value of our bitmap. * In line 12 we change back our ready variable to false again, because now we need to do the same with the second thread, we need to know when it's ready. * In line 13 we unlock everything again. And the next lines does everything again, but for the second thread. === Two Threads Running=== At this point we have two threads running! Now what? Well those threas are changing the posi_X and posi_Y variables, so if you don't hurry up and draw the bitmap you're not going to see anything. But before drawing anything let's see how these threads are changing these variables: <code c> while(!al_get_thread_should_stop(thr)){ al_lock_mutex(data->mutex); if(modi_X) data->posiX += num; else data->posiY += num; al_unlock_mutex(data->mutex); al_rest(0.01); } return NULL; </code> We're back to our **Func_Thread()** function. After initializing some values and telling to our parent thread that we're ready, the thread enter in a loop. A very simple loop that you should understand very well. To keep it simple we're not going to create another timer but just use <code>al_rest()</code> which allow us to rest the thread some seconds, otherwise this loop would run so fast that we wouldn't be able to see our bitmap on the screen. <code c>while(!al_get_thread_should_stop(thr)) </code> The same way this thread talked with the parent thread to tell it was ready using the **cond** variable, this function **al_get_thread_should_stop(thr)** allows the parent thread to talk with this thread, but this time we use it to tell our thread that must stop. If **al_get_thread_should_stop(thr)** returns true, the thread will stop, how can we make this function to return true?, we'll see it later on. **thr** it's the other value that **Func_Thread** receives. It's nothing more than a pointer to the current thread. So we're done with the thread function. Now let's advance in our parent thread. <code c> while(1) { ALLEGRO_EVENT ev; al_wait_for_event(event_queue, &ev); if(ev.type == ALLEGRO_EVENT_TIMER) { redraw = true; } else if(ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { break; } else if(ev.type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN) { break; } if(redraw && al_is_event_queue_empty(event_queue)) { redraw = false; al_lock_mutex(data.mutex); float X = data.posiX; float Y = data.posiY; al_unlock_mutex(data.mutex); al_draw_bitmap(bouncer, X, Y, 0); al_flip_display(); } } al_destroy_thread(thread_1); al_destroy_thread(thread_2); al_destroy_bitmap(bouncer); al_destroy_timer(timer); al_destroy_display(display); al_destroy_event_queue(event_queue); </code> This shouldn't be anything new. As you can see in line 17, 18, 19 and 20, we are creating two variables and initializing them with the corresponding value, this is to clarify, that you should keep locked the mutex the less possible, **al_draw_bitmap()** take much more time than just creating and initializing two **float** variables, for that reason we're doing this. <code c> al_destroy_thread(thread_1); al_destroy_thread(thread_2); </code> And finally we're destroying the threads when we exit the program. This implicitly performs **al_join_thread** which at the same time implicitly calls **al_set_thread_should_stop** that way **al_get_thread_should_stop(thr)** will return true and the thread is going to stop. //Note: This is a very simple example to help you get the idea, normally you don't need to initialize first a thread then another one and so on, you can do whatever you want, though ;).// [[allegro:input|Précédent]] << [[allegro:start|Sommaire]] >> [[allegro:addons|Suivant]]

allegro/threads.1327419281.txt.gz · Dernière modification: 2012/01/24 16:34 par mrhide