Gamasutra.com Features - The Transition to Concurrency
It's free to join Gamasutra!|Have a question? Want to know who runs this site? Here you go.|Targeting the game development market with your product or service? Get info on advertising here.||For altering your contact information or changing email subscription preferences.
Registered members can log in here.Back to the home page.

Search articles, jobs, buyers guide, and more.

Gamasutra
April 5, 2007

The Transition to Concurrency

arrowrightPage One
arrowrightPage Two
arrowrightPage Three
arrowrightPage Four

Printer Friendly Version




Latest Letters to the Editor:
Perpetual Layoffs by Alexander Brandon [09.21.2007]

Casual friendliness in MMO's by Colby Poulson [09.20.2007]

Scrum deals and 'What is Scrum?' by Tom Plunket [08.29.2007]


[Submit Letter]

[View All...]
  


The Transition to Concurrency


Complex Classes and Data Structures

"You're asking me to copy everything every time I want to modify some value? I thought this was about gaining performance?!"

It is obviously costly to make deep copies, especially if you have complex classes and rely on classic mutable data structures. This is why you should consider using immutable data structures, sometimes referred to as persistent data structures.

Imperative programmers use arrays a lot but functional programmers tend to favor linked lists. If you have experience with functional programming, you have probably noticed that these guys seem obsessed with heads and tails, or CAR and CDR. Head simply means the first element in a linked list, and tail means the rest of the list.

Now, these simple operations have a great characteristic, namely that they give you a new value simply by copying a pointer, and without ruining the old value. The tail of an immutable list is another immutable list. And if you append this tail to a new head, you have a list with a new element without breaking the old one.

Of course, linked lists have well known disadvantages as well, such as the lack of random access. There are a number of tree structures such as red-black trees that come in handy in such cases. Currently, the standards have no concept of persistent containers and I haven't yet seen general-purpose libraries that supply them for C++, so you are somewhat on your own here. I expect this field to see a lot of action in the imminent years.

The Next Best Thing: Active Objects

If a piece of data needs to be shared, we need to make sure that access to it is sequential, even though it is accessed by parallel threads.

Erlang achieves this by the use of messages, which we can simulate in C++ with active objects. Active objects is a design pattern that merges the object and thread concepts into one. The object creates a new thread when instantiated, much like Erlang processes.

Instead of invoking methods on it, you send a message to it which goes into a FIFO queue. The object has a scheduler that continuously handles messages in the queue. Hence, the only thread actually accessing (or writing to, at least) the data is the owner thread. Other threads only tell it to do so.

If a return value is required, this is done using a future. A future is an object that will contain the result when ready. Paul Bridget has written a decent description of the active object design pattern with C++ implementation examples which can be found here.

Exception Safety

You may have noticed that besides opening up to concurrency, these coding styles share another property: they increase exception safety.

Making guarantees about exception safety is far from trivial. Also, game developers tend to avoid exceptions because of the performance overhead. This is a reasonable concern even though the inlining mechanisms in C++ help minimize it.

However, when you can form a reasonable invariant and convince yourself that it is maintained everywhere, you are not only on the way to exception safety but to more stabile code in general. Also, you will find that it lends itself to unit testing much better. Concurrent code, I find, is where unit testing generally fails to be of much help - at least for code based on semaphores.

However, an immutable class is easily tested and if it is really immutable, it will work concurrently as well. The same goes more or less for an active object.

Smells

"Ok, I kinda buy it. But in my current project though, that conversion could be done to maybe one or two percent of the classes", you may be thinking. (Why is it that without exception, when hearing of some interesting new paradigm or system you think "yeah, that makes sense. But it wouldn't apply to what I am working on right now"?)

Transforming imperative code into functional code is a major task and it may not be feasible for you. But you could have the ideas in mind next time you are going to write a class from scratch. There are a number of smells that you should be aware of.

1. Initialize Functions

I used to write these all the time. My idea was that an ideal class would have an initialize function which was called by the constructor but which you could also call yourself if you had to re-initialize it later on. I guess I thought of the class as a sort of container of contents, whereas I now see it more as a representation of the contents themselves.

Try to write your class so that it can only be initialized once: during construction. If you're writing C++, this also means that you don't lose the power of references which are immutable themselves (the actual reference, that is - not the referred variable). References can only be initialized with constructors.




join | contact us | advertise | write | my profile
news | features | companies | jobs | resumes | education | product guide | projects | store



Copyright © 2006 CMP Media LLC

privacy policy
| terms of service