Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Intro to threading and cpu free waiting for signals

This thread is locked; no one can reply to it. rss feed Print
Intro to threading and cpu free waiting for signals
Edgar Reynaldo
Member #8,592
May 2007
avatar

I need a way to wait in another thread for allegro's event queue to pop up with a timer event without disturbing the main thread but also at the same time being able to wake the main thread up if it receives an event.

Can anyone give me a simple walkthrough of how these things are done? Do I need allegro's threads, do I need mutexes (I'm guessing I probably do)?, do I need pthreads, or what? I think pthreads had something like a 'wait for signal' function but I can't remember what it was called now.

Anybody have some knowledge to share about these things?

Arthur Kalliokoski
Second in Command
February 2005
avatar

I need a way to wait in another thread for allegro's event queue to pop up with a timer event without disturbing the main thread but also at the same time being able to wake the main thread up if it receives an event.

I was under the impression that's what the message loop did by default, as long as there was an al_wait_for_event() to cause it to idle when no events occurred. I assume you want the main thread to sleep, since you specify "wake the main thread up".

“Throughout history, poverty is the normal condition of man. Advances which permit this norm to be exceeded — here and there, now and then — are the work of an extremely small minority, frequently despised, often condemned, and almost always opposed by all right-thinking people. Whenever this tiny minority is kept from creating, or (as sometimes happens) is driven out of a society, the people then slip back into abject poverty. This is known as "bad luck.”

― Robert A. Heinlein

taron 
Member #10,584
January 2009
avatar

I think you might want to use 2 event queues, one for each thread.
If the main thread needs to be woken up by your second thread, let the second thread send a message on the main thread's queue and the main thread will wake up. The second thread can listen for events on its own without having to disturb the main thread.

Thomas Fjellstrom
Member #476
June 2000
avatar

Yup, just send messages back and forth.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Edgar Reynaldo
Member #8,592
May 2007
avatar

Maybe I didn't explain well. I'm trying to implement my own event sources and I need the timer thread to sleep until the timer goes off, and then emit the timer event. As it is now I have to poll for the timer event in al_wait_for_event, but I want it asynchronous so I can just add the timer events to a message queue w/o disturbing the main thread.

Thomas Fjellstrom
Member #476
June 2000
avatar

That sounds less clear than your first question...

any thread can setup an event queue, and event sources, including timers. They can then wait on that queue.

Any thread can add events to a queue, which will only wake up other threads that are `al_wait_for_event`ing on that queue.

As it is now I have to poll for the timer event in al_wait_for_event

`al_wait_for_event` actually sleeps. It does not poll.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Edgar Reynaldo
Member #8,592
May 2007
avatar

I'm trying to accomplish what allegro does with its event queues, that is it wakes up the thread when the signal goes off.

Maybe I have to post example code to show what I mean. I'm not at home though so can't post it atm.

I have a timer class. I am trying to decide whether to make it an eagle event source or to have my event handler subscribe allegro's timer event source to its queue. If I make my timer an event source then I need to emit an event whenever the timer goes off. That means I have to call al_wait_for_event (and use up one thread) to poll for the timer event using the private allegro event queue and timer. That's what I meant by polling.

So I want to create a thread that will call al_wait_for_event to wait for a timer. I want that thread to access an event queue and post messages to it, what I call an event handler. So do I need a mutex to protect the queue while I am asynchronously adding an event to it? Does allegro's event queues use mutexes?

Thomas Fjellstrom
Member #476
June 2000
avatar

Event queues are thread safe. You don't need to worry about locking around them afaik.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Edgar Reynaldo
Member #8,592
May 2007
avatar

No, do I need a mutex for my eagle event queues, not for an allegro event queue.

I want one thread to wait on the timer, and then emit events whenever it goes off. However the event queue I want it to post messages to is on a different thread, hence the question about the mutex. I don't want to access the event queue at the same time I am adding an event to it. Do I need a mutex to keep my eagle event queues thread safe?

Thomas Fjellstrom
Member #476
June 2000
avatar

Oh, it depends on how you implemented your event queues. You can implement lockless queue algorithms, but I'm doubting you did so. so yes, you probably do want locking around access to your queue's internals.

There are also other locking primitives you could use, like condvars or semaphores.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

bamccaig
Member #7,536
July 2006
avatar

Sounds a bit like you're reinventing the square wheel that is Allegro's event queue. Is there a reason that you can't just use Allegro's event queues as suggested?

taron 
Member #10,584
January 2009
avatar

bamccaig said:

Sounds a bit like you're reinventing the square wheel that is Allegro's event queue. Is there a reason that you can't just use Allegro's event queues as suggested?

I believe his library is supposed to support multiple back-ends and I think he doesn't want the core to have to rely on allegro.

Edgar Reynaldo
Member #8,592
May 2007
avatar

bamccaig said:

Sounds a bit like you're reinventing the square wheel that is Allegro's event queue. Is there a reason that you can't just use Allegro's event queues as suggested?

Well actually yes I am a little bit. I just need mostly the same functionality with a small wrapper over it for abstraction and portabilities sake. I need my Timer class to be an event source but I don't want to poll for things. I want to post an event right away. Which means I need another thread to work alongside the main one, so I suppose I should wrap a Thread too. It's just adding another link in the chain of signals I guess. No biggie.

taron said:

I believe his library is supposed to support multiple back-ends and I think he doesn't want the core to have to rely on allegro.

You are correct sir.

You can look up my latest code on sourceforge through svn :

svn checkout "svn://svn.code.sf.net/p/eagle5gui/code/trunk eagle5gui-code"

or just the url (click on copy link location)
svn://svn.code.sf.net/p/eagle5gui/code/trunk eagle5gui-code

It's building now and I'm working on tests to make sure everything works. Adventurous souls welcome, but the only docs I have are the headers.

There is a simple example program that displays your input and the frames per second. The build system is currently CB Projects, so you would have to adjust your linking and search directories to match whatever version of A5 you have installed.

I think what I need are semaphores.

Arthur Kalliokoski
Second in Command
February 2005
avatar

I think what I need are semaphores.

AFAIK, semaphores might not work, what if each CPU has differing values in their respective caches? Of course, if you accessed this semaphore only through a mutex, it'd work.

“Throughout history, poverty is the normal condition of man. Advances which permit this norm to be exceeded — here and there, now and then — are the work of an extremely small minority, frequently despised, often condemned, and almost always opposed by all right-thinking people. Whenever this tiny minority is kept from creating, or (as sometimes happens) is driven out of a society, the people then slip back into abject poverty. This is known as "bad luck.”

― Robert A. Heinlein

Edgar Reynaldo
Member #8,592
May 2007
avatar

What do you mean, semaphores might not work? They're not reliable ways to sleep and wake up a thread?

Forgot to post example code :

#SelectExpand
1 2 3#include "RecordInputTestMain.hpp" 4 5#include "Eagle/backends/Allegro5Backend.hpp" 6 7#include <sstream> 8using std::ostringstream; 9#include <string> 10using std::string; 11 12int RecordInputTestMain(int argc , char** argv) { 13 14// if (!SendOutputToFile("RecordInputTestMain.txt" , "Record input test\n\n" , false)) {return 1;} 15 16 Allegro5System sys; 17 18 int start_state = EAGLE_FULL_SETUP; 19 if (sys.Initialize(start_state) != start_state) { 20 return -1; 21 } 22 23 int SCREEN_WIDTH = 800; 24 int SCREEN_HEIGHT = 600; 25 EagleGraphicsContext* win = sys.CreateGraphicsContext(SCREEN_WIDTH , SCREEN_HEIGHT , EAGLE_WINDOWED | EAGLE_OPENGL); 26 27 if (!win->Valid()) { 28 return -2; 29 } 30 31 EagleFont* font40 = win->LoadFont("Data/fonts/verdana.ttf" , 40 , 0 , VIDEO_IMAGE); 32 EagleFont* font20 = win->LoadFont("Data/fonts/verdana.ttf" , 20 , 0 , VIDEO_IMAGE); 33 34 if (!font40->Valid() || !font20->Valid()) { 35 return -3; 36 } 37 38 int count = 0; 39 int scount = 0; 40 bool quit = false; 41 bool redraw = true; 42 int fps = 0; 43 int framecount = 0; 44 45 Input press; 46 Input held; 47 InputGroup ig; 48 49 sys.GetSystemTimer()->Start(); 50 51 do { 52 53 if (redraw) { 54 55 ostringstream oss; 56 oss.str(""); 57 ig.ShowLogic(oss); 58 59 60 win->Clear(EagleColor(0,0,0)); 61 win->DrawTextString(font40 , StringPrintF("FPS : %i" , fps) , 40 , 40 , EagleColor(0,255,255)); 62 win->DrawTextString(font40 , StringPrintF("framecount = %i" , framecount + 1) , 40 , 80 , EagleColor(0,255,255)); 63 win->DrawTextString(font20 , oss.str() , 400 , 300 , EagleColor(255,255,255) , DRAW_TEXT_CENTER , DRAW_TEXT_TOP); 64 win->FlipDisplay(); 65 ++framecount; 66 redraw = false; 67 } 68 69 do { 70 EagleEvent ee = sys.UpdateSystemState(); 71 72 if (AnyInputPressed(&press)) { 73 if (ModifierHeld(&held)) { 74 ig = press && held; 75 } 76 else { 77 ig = InputGroup(press); 78 } 79 redraw = true; 80 } 81 82 if (ee.type == EAGLE_EVENT_TIMER && true/*ee.timer.source == sys.GetSystemTimer()*/) { 83 count += 1; 84 if (count == 60) { 85 scount += 1; 86 count = 0; 87 fps = framecount; 88 framecount = 0; 89 } 90// fps = count; 91 redraw = true; 92 } 93 else if (ee.type == EAGLE_EVENT_KEY_DOWN) { 94 if (ee.keyboard.keycode == EAGLE_KEY_ESCAPE) { 95 quit = true; 96 break; 97 } 98 } 99 if (ee.type == EAGLE_EVENT_DISPLAY_CLOSE) { 100 quit = true; 101 break; 102 } 103 104 } while (!sys.UpToDate()); 105 } while (!quit); 106 107 108 return 0; 109}

Arthur Kalliokoski
Second in Command
February 2005
avatar

I'm wrong, Wikipedia says you're right. I was thinking you just meant ordinary booleans (like a semaphore flag, on a mailbox or a railroad crossing).

“Throughout history, poverty is the normal condition of man. Advances which permit this norm to be exceeded — here and there, now and then — are the work of an extremely small minority, frequently despised, often condemned, and almost always opposed by all right-thinking people. Whenever this tiny minority is kept from creating, or (as sometimes happens) is driven out of a society, the people then slip back into abject poverty. This is known as "bad luck.”

― Robert A. Heinlein

Edgar Reynaldo
Member #8,592
May 2007
avatar

yeah, I meant a pthread semaphore.

I may just wrap Allegro's Thread routines. They seem solid enough to write a driver around.

Edit
Well, I did just that, and now I have a thread running my TimerProcess but everytime I run my test program, I get two assert failures that don't crash like they're supposed to, and the program keeps running in the background and doesn't return. In addition to that, there are two pointers that are null that should not be.

Allegro5Timer.cpp

#SelectExpand
1#include "Eagle/backends/Allegro5/Allegro5EventHandler.hpp" 2#include "Eagle/backends/Allegro5/Allegro5Timer.hpp" 3#include "Eagle/backends/Allegro5/Allegro5Threads.hpp" 4 5 6 7 8enum EAGLE_TIMER_MESSAGE_TYPE { 9 EAGLE_MESSAGE_START_TIMER = 1, 10 EAGLE_MESSAGE_STOP_TIMER = 2, 11 EAGLE_MESSAGE_CLOSE_TIMER = 3 12}; 13 14 15 16void* TimerProcess(EagleThread* ethread , void* etimer) { 17 EagleTimer* eagle_timer = (EagleTimer*)etimer; 18 Allegro5Timer* eagle_a5_timer = dynamic_cast<Allegro5Timer*>(eagle_timer); 19 EAGLE_ASSERT(eagle_a5_timer); 20 21 ALLEGRO_TIMER* timer = eagle_a5_timer->AllegroTimer(); 22 ALLEGRO_EVENT_QUEUE* queue = eagle_a5_timer->AllegroEventQueue(); 23
24 EAGLE_ASSERT(timer);
25 EAGLE_ASSERT(queue);
26 27 int counter = 0; 28 bool close = false; 29 while (!ethread->ShouldStop() && !close) { 30 ALLEGRO_EVENT ev;
31 al_wait_for_event(queue , &ev);
32 OutputLog() << "Event " << ev.type << std::endl; 33 if (ev.type == ALLEGRO_EVENT_TIMER && ev.timer.source == (ALLEGRO_TIMER*)eagle_a5_timer->Source()) { 34 ++counter; 35 eagle_a5_timer->Tick(al_get_time()); 36 } 37 else if (ev.type == EAGLE_EVENT_USER_START) { 38 switch (ev.user.data1) { 39 case EAGLE_MESSAGE_START_TIMER : 40 al_start_timer(timer); 41 break; 42 case EAGLE_MESSAGE_STOP_TIMER : 43 al_stop_timer(timer); 44 break; 45 case EAGLE_MESSAGE_CLOSE_TIMER : 46 close = true; 47 break; 48 default : EAGLE_ASSERT(0); 49 break; 50 } 51 } 52 } 53 return (void*)counter; 54} 55 56 57 58void Allegro5Timer::SendTimerProcessMessage(int message) { 59 ALLEGRO_EVENT ev; 60 ev.type = EAGLE_EVENT_USER_START; 61 ev.user.data1 = message;
62 al_emit_user_event(&event_source , &ev , NULL);
63} 64 65 66 67Allegro5Timer::Allegro5Timer() : 68 EagleTimer(), 69 timer(0), 70 queue(0), 71 queue_lock(0), 72 event_source(), 73 ethread(0) 74{
75 al_init_user_event_source(&event_source);
76} 77 78 79 80Allegro5Timer::~Allegro5Timer() { 81 Destroy(); 82} 83 84 85 86bool Allegro5Timer::Create(double seconds_per_tick) { 87 EAGLE_ASSERT(seconds_per_tick > 0.0); 88 89 Destroy(); 90 91 timer = al_create_timer(seconds_per_tick); 92 queue = al_create_event_queue(); 93 ethread = new Allegro5Thread(); 94 95 EAGLE_ASSERT(timer); 96 EAGLE_ASSERT(queue); 97 98 if (queue && timer) { 99 spt = seconds_per_tick; 100 previous_ticks = current_ticks = al_get_timer_count(timer); 101 al_register_event_source(queue , al_get_timer_event_source(timer)); 102 al_register_event_source(queue , &event_source); 103 /// MUST create TimerProcess thread AFTER registering event sources or it will wait forever
104 ethread->Create(TimerProcess , this);
105 if (ethread->Valid()) { 106 ethread->Start(); 107 return true; 108 } 109 } 110 111 if (!queue) { 112 OutputLog() << "Allegro5Timer::Create - Could not create an Allegro 5 Timer - Couldn't create an ALLEGRO_EVENT_QUEUE." << std::endl; 113 } 114 if (!timer) { 115 OutputLog() << "Allegro5Timer::Create - Could not create an Allegro 5 Timer - Couldn't create an ALLEGRO_TIMER." << std::endl; 116 } 117 if (!ethread->Valid()) { 118 OutputLog() << "Allegro5Timer::Create - ethread is not valid." << std::endl; 119 } 120 121 // The queue or the timer failed to be created 122 Destroy(); 123 return false; 124} 125 126 127 128void Allegro5Timer::Destroy() { 129 if (ethread && running) { 130 SendTimerProcessMessage(EAGLE_MESSAGE_CLOSE_TIMER); 131 ethread->Join(); 132 } 133 if (queue) { 134 al_destroy_event_queue(queue); 135 queue = 0; 136 } 137 if (timer) { 138 al_destroy_timer(timer); 139 timer = 0; 140 } 141 if (ethread) { 142 delete ethread; 143 ethread = 0; 144 } 145 spt = 0.0; 146} 147 148 149 150void Allegro5Timer::Start() { 151 SendTimerProcessMessage(EAGLE_MESSAGE_START_TIMER); 152} 153 154 155 156void Allegro5Timer::Stop() { 157 SendTimerProcessMessage(EAGLE_MESSAGE_STOP_TIMER); 158} 159 160 161 162void Allegro5Timer::WaitForTick() { 163 if (queue && timer) { 164 do { 165 ALLEGRO_EVENT e; 166 al_wait_for_event(queue , &e); 167 if (e.type == ALLEGRO_EVENT_TIMER) { 168 Tick(al_get_time()); 169 break; 170 } 171 } while (true); 172 } 173 return; 174} 175 176 177 178void* Allegro5Timer::Source() { 179 return timer; 180} 181 182 183 184void Allegro5Timer::RefreshTimer() { 185 if (queue && timer) { 186 ALLEGRO_EVENT ev; 187 while (al_get_next_event(queue , &ev)) { 188 if (ev.type == ALLEGRO_EVENT_TIMER) { 189 Tick(al_get_time()); 190 } 191 } 192 } 193} 194 195 196 197bool Allegro5Timer::Valid() { 198 return timer && queue && ethread->Valid(); 199} 200 201 202 203long long int Allegro5Timer::Count() { 204 if (timer) {return al_get_timer_count(timer);} 205 return -1; 206} 207 208 209 210void Allegro5Timer::RegisterTimerInput(EagleEventHandler* event_handler) { 211/* 212 EAGLE_ASSERT(event_handler); 213 Allegro5EventHandler* a5_event_handler = dynamic_cast<Allegro5EventHandler*>(event_handler); 214 EAGLE_ASSERT(allegro_handler); 215 216 ALLEGRO_EVENT_QUEUE* allegro_queue = a5_event_handler->AllegroQueue(); 217 EAGLE_ASSERT(allegro_queue); 218 al_register_event_source(allegro_queue , al_get_timer_event_source(timer)); 219*/ 220 // so we don't have timers registered to queues and eagle timers registered as event sources at the same time 221 SubscribeListener(event_handler);/// TODO Convert to an event source 222}

I marked all the lines I thought were important. Does anybody see anything wrong with my logic? I make a new thread in Allegro5Timer::Create, make an allegro timer and queue, and make sure they're both valid and register the event sources and then and only then do I create and start the thread that runs TimerProcess, where I keep getting the two marked assertion failures that the timer and queue are both null. I don't get it.

Go to: