Threads with SDL within C++ classes

Started by
10 comments, last by TheComet 9 years, 6 months ago

I'm planning on trying out SDL 1.2 threads for my cross-platform game to run some specific tasks. At first a terrain data streamer (even though there may be better ways to do this directly). I'm trying first to figure out how to make a C++ class based design so that the required mutexes would be contained within the class and it would be safe to use the class interface.

Below I tried to envision a class for processing some data (here just an array of integers) by a separate thread. The idea is that the main thread creates an instance of the class and then requests work to be done by method requestWorkToBeDone(). The main thread can then ask for the status of the work by getStatus() and acquire the processed data by getData(). The class should be responsible for appropriate mutexes.


enum EStatus {
 statusIdle,
 statusWorking,
 statusDone
};

class MyClass
{
public:
 ~MyClass();
 bool init(); // Create thread and mutex, allocate data.

 static int threadFuncMediator(void *p) // called by SDL_CreateThread to run threadFunc() in separate thread.
 {
  return (static_cast<MyClass*>p)->threadFunc();
 }

 void getStatus(EStatus &_status) const
 {
  SDL_mutexP(mutex);
  _status = status;
  SDL_mutexV(mutex);
 }

 bool requestWorkToBeDone(int someParameter)
 {
  SDL_mutexP(mutex);
  if(status!=statusIdle){
   SDL_mutexV(mutex);
   return false;
  }

  status = statusWorking;

  // Use someParameter to specify work ...

  SDL_mutexV(mutex);

  return true;
 }

 bool getData(int *_data)
 {
  SDL_mutexP(mutex);
  if(status!=statusDone){
   SDL_mutexV(mutex);
   return false;
  }
  memcpy(_data, data, sizeof(int)*numData);
  status = statusIdle;
  SDL_mutexV(mutex);

  retrurn true;
 }
private:
 SDL_Thread *thread;
 SDL_mutex *mutex;

 EStatus status;

 int numData;
 int *data;

 // This runs in a thread created by init() via threadFuncMediator().
 // Calls getStatus() to see if work should be done.
 // When work is done, calls setStatus(statusDone);
 int threadFunc();
};

The thing I'm worried is that threadFunc() also has to call getStatus() to see if it should do work or not. But the instance of the class is owned by the main thread, so is this function call itself safe, eventhough data handling within the function is handled with mutexes?

Advertisement

I'm planning on trying out SDL 1.2 threads


Drop 1.2, use the maintained and superior 2.0.

Below I tried to envision a class for processing some data (here just an array of integers) by a separate thread. The idea is that the main thread creates an instance of the class and then requests work to be done by method requestWorkToBeDone(). The main thread can then ask for the status of the work by getStatus() and acquire the processed data by getData(). The class should be responsible for appropriate mutexes.


This is not at all how you want to do threading. Spinning up a whole thread is _expensive_. Doing it every time you need work done would be very slow. You want to have a pool of worker threads you can tap into when needed.

You don't need mutexes for every operation, either. Again, they are slow, and using them poorly can also result in your program being slower than it would be without threading. Done right, you need _zero_ mutexes, even with a dozen threads.

Blocking on the thread is also rather useless. Why even fork into another thread if you're just going to wait for it?

There's a ton of articles, books, blog posts, and forum posts right on this site that explain how you should do threading. Don't reinvent the wheel. (Sorry, it's early and I have to get ready for work or I'd curate a few of the better ones for you; I'd really just start reading through some of those Google results, though.)

Sean Middleditch – Game Systems Engineer – Join my team!


Drop 1.2, use the maintained and superior 2.0.

I will soon enough. At the moment I'm still working on Ubuntu 12.04 whose repository only contains SDL 1.2.


This is not at all how you want to do threading. Spinning up a whole thread is _expensive_. Doing it every time you need work done would be very slow. You want to have a pool of worker threads you can tap into when needed.

Perhaps I did not express my intention sufficiently clearly. My intention was to create an instance of the class and setup the thread once. The main thread then calls the method requestWorkToBeDone each time it needs work to be done. The thread is never recreated but rather stays idle if no tasks have been specified.


Blocking on the thread is also rather useless. Why even fork into another thread if you're just going to wait for it?

Nothing would be blocked. The main thread would call getStatus() once during update loop to check if a work has been finished, but otherwise continue doing other things.

You don't need mutexes for every operation, either. Again, they are slow, and using them poorly can also result in your program being slower than it would be without threading. Done right, you need _zero_ mutexes, even with a dozen threads.

But the threads need to communicate at some point. I would think that usually by reading/writing some variable, whence mutexes must be used.

You do not need mutexrs to communicate. You can use message queues, atomics, RCU, etc. to communicate. You can use futures and continuations instead of polling a getStatus function.

If you build out threading in a game well then this whole class is still unnecessary. Build a generic thread pool. Build a job system. Dispatch jobs to the pool and use futures or job groups for synchronization. Or just use Intel TBB or the like instead of (poorly) recreating the wheel.

Sean Middleditch – Game Systems Engineer – Join my team!

I will soon enough. At the moment I'm still working on Ubuntu 12.04 whose repository only contains SDL 1.2.

You can get the sources for SDL 2 on libsdl.org. I have not built it on Ubuntu yet but this guide should tell you everything you need.

There is no need to stick with old and outdated software and/or SDKs just because the newer versions aren't included in the official repositories. ;)


You do not need mutexrs to communicate. You can use message queues, atomics, RCU, etc. to communicate. You can use futures and continuations instead of polling a getStatus function.

If you build out threading in a game well then this whole class is still unnecessary. Build a generic thread pool. Build a job system. Dispatch jobs to the pool and use futures or job groups for synchronization. Or just use Intel TBB or the like instead of (poorly) recreating the wheel.

Thanks for the hints. I have had the feeling that one does not simply add threads to a game. My goal is _not_ to support threading in a general manner in my game to perform per-frame tasks. I would only use threads to perform very specific jobs that are not required to complete within a frame. Examples are paging some terrain block data and updating impostors when camera moves or lighting changes. These may take a few frames to complete and the game will just use the old resources until the jobs are done. I would think that it is easier to use threading (read: apply even a naïve approach) for such jobs so that they actually yield performance improvement.


You can get the sources for SDL 2 on libsdl.org. I have not built it on Ubuntu yet but this guide should tell you everything you need.


There is no need to stick with old and outdated software and/or SDKs just because the newer versions aren't included in the official repositories. ;)

This is a painful topic. The idea of repositories is that you can install stuff easily and in the same way, not spending hours hunting down build dependencies. Sometimes I just have to install something out of repositories to get a latest version, but I consider this the last resort. I will be updating to the latest LTS version soon and hope that SDL 2.0 is found in the repos.

By googling "thread using future" I found this: http://www.justsoftwaresolutions.co.uk/threading/multithreading-in-c++0x-part-8-futures-and-promises.html

which has an example of using promises and futures for implementing some sort of asynchronous IO. This is pretty close to what I would like to do. In my case, I would create the instance async_io, which is kept for the whole timespan of the game and queue_read() would be used to request asynchronous reads. The only problem is that in this sample, the get() function of the future is used. If I understand correctly, this will block until the job is done. This does not work for a game. Can I just call the valid() method of the future to check if the work is done, and if true, then call get() without getting blocked? Anyway, this is quite close to what I was aiming at in my example, with the difference that by using futures I don't have to rely on my "skills" of using low level stuff like mutexes.

Building SDL2 from source is likely easy on Ubuntu, and I imagine you can probably find a .deb file or a PPA if you want to install it through apt-get or whatever.

This is a painful topic. The idea of repositories is that you can install stuff easily and in the same way, not spending hours hunting down build dependencies. Sometimes I just have to install something out of repositories to get a latest version, but I consider this the last resort. I will be updating to the latest LTS version soon and hope that SDL 2.0 is found in the repos.

The idea of having a supported archive of product is a world-view foreign to many who grew up unfamiliar with such alternatives. Don't worry about their limitations.

You really should upgrade to Ubuntu 14.04 as soon as you can. Yes, libSDL2 is available there, and it's already ported natively to the Mir display server so you can develop targeting desktop, tablet, and phone right away. There's really no reason to hold back.

Stephen M. Webb
Professional Free Software Developer

Can I just call the valid() method of the future to check if the work is done, and if true, then call get() without getting blocked? Anyway, this is quite close to what I was aiming at in my example, with the difference that by using futures I don't have to rely on my "skills" of using low level stuff like mutexes.


Valid does not do what you think it does, it does not signal when the future has a result. However, you could probably use one of the timed wait functions with an immediate timeout.

This topic is closed to new replies.

Advertisement