And I use the term "serialize inter-thread communcations" loosely, but pointedly:
It could include message passing; it could include coded commands, functors, whatever;
but it could also include my invention of a few years back (it's probably been invented
already, but anyhow), namely an inter-thread memory manager that has two equally
sized memory blocks A and B. At first, thread a has read/write access to block A, and
read-only access to block B, while consumer thread b only has has read access to
block B. Once both threads report they are done for that frame, the two blocks are
swapped, so that whatever thread a wrote to block A now becomes available to
thread b, and thread a can overwrite block B.
The negotiation and block swap needs a mutex or interlock, but it is a mutex that is
only suffered once per frame, as opposed to having mutexes all over the code.
A few years ago, I took two packages from BOOST and combined them:
- A fast, block allocator.
- The (then incomplete and abandoned) thread library.
And what I put together was an ultra-fast, per-thread, block allocator; with the
allocator class being a thread-local object.
Then I tuned performance even more using AMD's Code Analyst. I could maybe
find the code, though it'd be obsolete, given that boost has a brand new thread
library.
But anyhow, I was working also on overriding the Delete operator for pointers
issued by these allocators, so as to have memory returned to the allocator of the
thread that produced them, upon deletion, and then let the allocator recycle the
memory if possible; point being no cumulative cross-thread memory migration.
Just mentioning this here to drive the point that what we need is to identify
the islands, or continents first, of code sharing or needing to share memory, and
where the minimal interfaces lie; turn those interfaces into thread secure comm
channels, and make sure each continent or island's memory is tightly isolated
from the others. Within each island, NOTHING needs to be thread-safe, because
you know that only one thread has access.
THAT is the way to do multi-threading; NOT by using thread-safe everything.
EDIT:
But anyhow, that's food for thought for the future. Right now, the first thing is
to clean up the code (making all class members private, const correctness,
virtualizing dtors, expliciting single arg ctors, using better names for classes,
identifying packages (sets of classes that work together) and putting them into
folders, adding comments to all files).
Then comes refactoring (limiting files to one class in public space per .h/.cpp,
removing unnecessary includes, moving includes from .h to .cpp, using pimpl's
in many places, removing dead code and obsolete comments, replacing pointers
with references where possible, or with smart pointers otherwise, identifying
general intents and using standard design patterns where applicable, adding
DBC (design by contract) basic constructs, such as asserts on all functions, and
class invariant checks, etceteras).
Then comes memory space refactorings: Identifying packages that need to
share memory and packages that don't, and identifying the "islands" we can
then run in separate threads; building thread-safe bridges between them,
and separating the threads.
Of course, bug-fixing and new features will have to run in parallel with all
the above; --can't just stop progress until all the refactoring is done.