views:

376

answers:

6

When double-buffering data that's due to be shared between threads, I've used a system where one thread reads from one buffer, one thread reads from the other buffer and reads from the first buffer. The trouble is, how am I going to implement the pointer swap? Do I need to use a critical section? There's no Interlocked function available that will actually swap values. I can't have thread one reading from buffer one, then start reading from buffer two, in the middle of reading, that would be appcrash, even if the other thread didn't then begin writing to it.

I'm using native C++ on Windows in Visual Studio Ultimate 2010 RC.

+1  A: 

Why can't you use InterlockedExchangePointer ?

edit: Ok, I get what you are saying now, IEP doesn't actually swap 2 live pointers with each other since it only takes a single value by reference.

John Knoeller
If you InterlockedExchangePointer, you can only actually affect one pointer at a time in a call. That means that in order to exchange all three pointers, I'd need three calls - leaving a gap in the middle.InterlockedExchangePointer is actually a misnomer, it will only set a given pointer to a given value. I expected that it would exchange a pair of pointers (for exactly this reason). Even if it did work like that, I still couldn't actually use it without pausing both threads to do the update, something I'd really rather avoid.
DeadMG
@DeadMG: `buf1 = InterlockedExchangePointer(` If you call that from the thread currently using buf1, I think it will do what you want. But I'm not entirely sure what you mean by "all three pointers".
Dan
I have one to bufferone from the render, one to bufferone from the game logic (so they can both read bufferone), then one to buffertwo for the game logic to write to. The thing is that the equivalence operator you called is not necessarily atomic - there's a gap between InterlockedExchangePointer and buf1 =. Even if there wasn't, there's still no mechanism here to make sure I'm not midway through rendering when this occurs.I think that the read-write mutex solution described in another answer was probably the best.
DeadMG
+5  A: 

Using critical sections is the accepted way of doing it. Just share a CRITICAL_SECTION object between all your threads and call EnterCriticalSection and LeaveCriticalSection on that object around your pointer manipulation/buffer reading/writing code. Try to finish your critical sections as soon as possible, leaving as much code outside the critical sections as possible.

Even if you use the double interlocked exchange trick, you still need a critical section or something to synchronize your threads, so might as well use it for this purpose too.

Blindy
I may be misunderstanding the OP's question... Won't a CRITICAL_SECTION result in serializing all access to the buffers, thus defeating the purpose of having two of them? I thought the idea was to have two buffers so each thread could have something to work on at the same time, and occasionally the buffers would get swapped.
Dan
Well having 2 buffers would still help in certain circumstances. Think of double buffering swap chains for example.
Blindy
You don't hold it while accessing the data, only swapping them.
DeadMG
You must use a Critical Section. The reason is that this can correctly follow language scope. As a result, it can correctly cover temporaries such `(buf[42])`. This is of course `*(buf+42)`, and if you'd swap `buf` after calculating the temporary (buf+42) but before dereferencing it, you've got a problem. In addtion, Critical Sections also give you the needed memory fences i.e. cache flushes.
MSalters
+1  A: 

You have to build your own function to swap the pointers which uses a semaphore or critical section to control it. The same protection needs to be added to all users of pointers, since any code which reads a pointer which is in the midst of being modified is bad.

One way to manage this is to have all the pointer manipulation logic work under the protection of the lock.

wallyk
A: 

See, I did originally design the threads so that they would be fully asynchronous and don't require any synchronizing in their regular operations. But, since I'm performing operations on a per-object basis in a thread pool, if a given object is unreadable because it's currently being synced, I can just do another while I'm waiting. In a sense, I can both wait and operate at the same time, since I have plenty of threads to go around.

Create two critical sections, one for each of the threads. While rendering, hold the render crit section. The other thread can still do what it likes to the other crit section though. Use TryEnterCriticalSection, and if it's held, then return false, and add the object in a list to be re-rendered later. This should allow us to keep rendering even if a given object is currently being updated. While updating, hold both crit sections. While doing game logic, hold the game logic crit section. If it's already held, that's no problem, because we have more threads than actual processors. So if this thread is blocked, then another thread will just use the CPU time and this doesn't need to be managed.

DeadMG
I don't think you can maintain this zero-synchronization approach for too long. You've obviously hit a brick wall here, and there might be bugs lingering in your code as well, but in general updating the game state requires some sort of a lock to not invalidate the rendering state mid-render.
Blindy
What code? :P I only render a simple few text objects right now, to test the multithreading design. I really don't want to write a renderer, then have to go back and write it again to be multithreaded.I'll go see what I can accomplish with the locks now.
DeadMG
A: 

You haven't mentioned what your Windows platform limitations are, but if you don't need compatibility with older versions than Windows Server 2003, or Vista on the client side, you can use the InterlockedExchange64() function to exchange a 64 bit value. By packing two 32-bit pointers into a 64-bit pair structure, you can effectively swap two pointers.

There are the usual Interlocked* variantions on that; InterlockedExchangeAcquire64(), InterlockedCompareExchange64(), etc...

If you need to run on, say, XP, I'd go for a critical section. When the chance of contention is low, they perform quite well.

aalpern
I'd happily consider not running on XP. But, it doesn't solve the problem of swapping the pointers mid-render.
DeadMG
+2  A: 

This sounds like a reader-writer-mutex type problem to me.

    [ ... but I mostly do embedded development so this may make no sense for a Windows OS. Actually, in an embedded OS with a priority-based scheduler, you can do this without any synchronization mechanism at all, if you guarantee that the swap is atomic and only allow the lower-priority thread to swap the buffers. ]

Suppose you have two buffers, B1 and B2, and you have two threads, T1 and T2. It's OK if T1 is using B1 while T2 is using B2. By "using" I mean reading and/or writing the buffer. Then at some time, the buffers need to swap so that T1 is using B2 and T2 is using B1. The thing you have to be careful of is that the swap is done while neither thread is accessing its buffer.

Suppose you used just one simple mutex. T1 could acquire the mutex and use B1. If T2 wanted to use B2, it would have to wait for the mutex. When T1 completed, T2 would unblock and do its work with B2. If either thread (or some third-party thread) wanted to swap the buffers, it would also have to take the mutex. Thus, using just one mutex serializes access to the buffers -- not so good.

It might work better if you use a reader-writer mutex instead. T1 could acquire a read-lock on the mutex and use B1. T2 could also acquire a read-lock on the mutex and use B2. When one of those threads (or a third-party thread) decides it's time to swap the buffers, it would have to take a write-lock on the mutex. It won't be able to acquire the write-lock until there are no more read-locks. At that point, it can swap the buffer pointers, knowing that nobody is using either buffer because when there is a write-lock on the mutex, all attempts to read-lock will block.

Dan
An interesting solution - pretty much the same as I had earlier, except cleaner. However, I can't find any docs on MSDN about such mutexes. Are they an inherent part of WinAPI, or would I have to code my own?
DeadMG
Beats me. I Googled `reader writer mutex windows` and found this: http://msdn.microsoft.com/en-us/magazine/cc163599.aspx -- helpful? Google without the `windows` to read up on theory...
Dan