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. FIXME : code en C, pas de C++. ==== 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> class DATA{ public: ALLEGRO_MUTEX *mutex; ALLEGRO_COND *cond; float posiX; float posiY; bool modi_X; bool ready; DATA() : mutex(al_create_mutex()), cond(al_create_cond()), posiX (0), posiY (0), modi_X(false), ready (false) {} ~DATA(){ al_destroy_mutex(mutex); al_destroy_cond(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) { 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; 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); 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); 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); return 0; } 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; } </file> ==== Détails ==== <code c> class DATA{ public: ALLEGRO_MUTEX *mutex; ALLEGRO_COND *cond; float posiX; float posiY; bool modi_X; bool ready; DATA() : mutex(al_create_mutex()), cond(al_create_cond()), posiX (0), posiY (0), modi_X(false), ready (false) {} ~DATA(){ al_destroy_mutex(mutex); al_destroy_cond(cond); } }; </code> First we create a class, which is going to contain the data we want to exchange between threads. <code c>ALLEGRO_MUTEX *mutex</code> Here we declare a mutex (mutual exclusion). In essence it's a flag that's supposed to tell other threads to leave some data alone. You can't set it directly, though. This should become clear why. <code c>ALLEGRO_COND *cond;</code> Here we declare a condition. It is sort of a sign that can asynchrously be picked up by other threads. Again you'll have to use functions to use it. <code c> float posiX; float posiY; bool modi_X; bool ready;</code> **posiX**, and **posiY** are the X, and Y coordinates of our bitmap, **modi_X** and **ready** are two flags that we're going to use later on. <code c> DATA() : mutex(al_create_mutex()), cond(al_create_cond()), posiX (0), posiY (0), modi_X(false), ready (false) {} ~DATA(){ al_destroy_mutex(mutex); al_destroy_cond(cond); } </code> Class's constructor, using an initialization list. Followed by the destructor. //Note: you cannot make an instance of this object on global scope, because **al_create_mutex** and **al_create_cond** would be called before **al_init()**.// <code c> ALLEGRO_THREAD *thread_1 = NULL; ALLEGRO_THREAD *thread_2 = NULL; </code> Then inside the **int main()** function we create a pointer to an **ALLEGRO_THREAD**, an opaque structure representing a thread. A thread is like a program running separately. <code c>DATA data;</code> After the Allegro initialization process, since it's using some allegro functions, we create our object **data**. <code c>thread_1 = al_create_thread(Func_Thread, &data); al_start_thread(thread_1);</code> Here we create our thread and immediately the data is sent. **Func_Thread** is a pointer to a function and **&data** is the address of our recently created object. And since when a thread is created, it is initially in a suspended state, we need to call **al_start_thread(thread_1)** to begin its actual execution. So, you have to write your own function that matches this prototype: <code c> static void *Func_Thread(ALLEGRO_THREAD *thr, void *arg) </code> Whatever you do inside this functions is going to be running on a different thread. Next lines get technical, but can be seen as 1 unit: <code c> al_lock_mutex(data.mutex); while (!data.ready){ al_wait_cond(data.cond, data.mutex); } al_unlock_mutex(data.mutex); </code> Firstly, we lock the mutex, this is how we tell Allegro that one of our many threads is going to use a shared resource.\\ An analogy would be shouting "//it's my turn now!//". If this thread is the first one to shout, it gets the mutex.\\ If another thread has locked the mutex first, this thread will have to wait until that particular thread unlocks it.\\ **while (!data.ready){** First of all, we need to check out if the flag ready is true, because in case it is, we don't need to wait for any condition, since for some reason the second thread previously created was faster than the parent thread and it's ready.\\ In case ready isn't true **al_wait_cond** will unlock the previously locked mutex and pauses this thread until the condition is signaled.\\ The function will return when cond is signaled, acquiring the lock on the mutex in the process. Finally since **al_wait_cond** acquired the mutex again, we need to call **al_unlock_mutex(data.mutex)** to unlock the 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. Now you should understand the reason a **while** loop was put around **al_wait_cond**; and a variable named **ready** was put into the **DATA** class. <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> 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.1324328124.txt.gz · Dernière modification: 2011/12/19 21:55 par mrhide