Skip to main content
GameDev.net gamedev.net
🔒 Locked

Global class instance or static singleton?

Started by skyfire360 Jan 17, 2008 at 2:16 PM 9 replies 3.6k views
Original Post
skyfire360
skyfire360
Say you have an object that is shared by many, many different objects in your program. Is it generally better practice to declare the object as a global and call it directly or to use a static member variable that returns a singleton?

// This
MyClass globalMyClass;

// or this
class MyClass {
public:
    static MyClass* MyClass::Instance() { return mSingleton; } 
      // I know I'm not checking if it's set, its just an example :)
private:
    MyClass *mSingleton;
};

I do real things with imaginary numbers
Zakwayda
Zakwayda
Quote:
Original post by skyfire360
Say you have an object that is shared by many, many different objects in your program.
What is the object? Who is it shared by? And why do so many different objects need to know about it?
skyfire360
skyfire360
Quote:
Original post by jyk
What is the object? Who is it shared by? And why do so many different objects need to know about it?


It is a lockless queue that is shared by multiple threads. Any object can push a 'job' onto the queue to be processed. Worker threads take jobs off the queue and process them.

I do real things with imaginary numbers
Evil Steve
Evil Steve
Quote:
Original post by skyfire360
It is a lockless queue that is shared by multiple threads. Any object can push a 'job' onto the queue to be processed. Worker threads take jobs off the queue and process them.
Then what's wrong with passing a reference to the queue to the thread constructor?
emeyex
emeyex
You're playing with fire to post about singletons around here :)... If you search gamedev for "singletons" it'll turn up several long, heated debates... which I'll stay out of for now.
jpetrie
jpetrie
Quote:

It is a lockless queue that is shared by multiple threads. Any object can push a 'job' onto the queue to be processed. Worker threads take jobs off the queue and process them.

Especially in a multithreaded context, it seems particularly fishy to be allowing anything to create new jobs (although being lockless may at least alleviate some of the horrid locking problems that could occur). If I were you, the first thing I'd do is start looking for ways to decrease the number of objects that "have to" have access to this job queue -- surely not everything in the entire codebase needs it.

It would help if we had more information about the system, we might be able to provide examples.
skyfire360
skyfire360
Quote:
Original post by Evil Steve
Then what's wrong with passing a reference to the queue to the thread constructor?


That is indeed necessary for the threads to know about the queue. That allows any worker thread to pop a job off the stack and process it. However, the worker threads themselves do not push jobs onto the queue. Any object in the program can encounter a situation that may require the creation of a job, and needs some way to communicate these newly created jobs to the worker threads.

Edit: Thanks for the comment jpetrie. I'm on my way back from work at the moment, but I will be more than happy to reply and elaborate on the system I'm working on.
I do real things with imaginary numbers
skyfire360
skyfire360
My idea is to try to create a multithreaded program that gets entirely away from the one-task-per-thread and move towards data-driven multithreading. My near-term goal with it will be to create a multithreaded medical signal analysis program, but I'd like to extend the ideas and the base platform someday into my hobby of graphics.

I originally started writing a graphics engine about a year ago as my first real attempt at designing a multithreaded program. I got up to the point where I had graphics in one thread, physics in another, and input/sound/logging in their own separate threads. However, this really didn't lend itself to much of a speedup, as the major cpu-intensive threads were physics and graphics; the other threads spent most of their time sleeping.

I opted to try something suggested by both AndyTX and andi over at the IGDA forums. You can find their posts here. In particular, what andi said really hit home with the job queue and how it can be applied to parts of the engine.

Quote:
andi over at IGDA said
so the broad phase basically creates your collision islands, at this point you pair up your collisions, these are effectively your job tasks. You then schedule your jobs so that collisions are resolved between them. I am oversimplifying again, sorry, but I am pretty sure havok will go more details, and they do normally have whitepapers on their approaches to solving problems.


I interpreted that to mean that one 'job' would be the calculation of the collision islands. Within that job, you would set up tasks of resolving paired (or multiple, depending on how much overhead the job creation made) collisions that resulted from the collision islands. I'm quite open to more elegant ways of creating jobs based upon data/objects, but the way I was approaching it spurred the initial question.
I do real things with imaginary numbers
Sneftel
Sneftel
For this sort of thing, I'd actually recommend a global variable. Those are good for objects which, by their nature, are necessarily separate from your carefully constructed class architecture, providing services to the language rather than to the application. A logger is a good example of this.
Antheus
Antheus
Quote:
Original post by Sneftel
For this sort of thing, I'd actually recommend a global variable. Those are good for objects which, by their nature, are necessarily separate from your carefully constructed class architecture, providing services to the language rather than to the application. A logger is a good example of this.


I'd be against it, but due to personal experience with such models (stack-less, event/message based and active object systems).

You'll never need two separate queues. Except for that one time, when you will.

A typical case of this would be in simulation environment. Entire game runs in single context. But when you try to add database query, you might find that the API doesn't support non-global access (grrr at mysql). In which case, it's necessary to simulate it via threads.

While in general this isn't a reason enough, the mysql experience alone has taught me that no assumptions can be made about globals, and in that particular case, in combination with boost::asio dispatcher, it was necessary to completely isolate the two by running them in separate io_services (what the proposed queue is here).

I'll fully agree that this comes closest to a global, but out of sheer practical considerations, having the option to have not only one ever and always, but one, two or three, if only very rarely.

I'll still stand that concurrency and globals don't mix.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.