views:

129

answers:

6

Let's say I have two arrays:

int[] array1 = new int[2000000];
int[] array2 = new int[2000000];

I stick some values into the arrays and then want to add the contents of array2 to array1 like so:

for(int i = 0; i < 2000000; ++i) array1[i] += array2[i];

Now, let's say I want to make processing faster on a multi-processor machine, so instead of just doing a loop like above, I create two threads. One of which I have process the first 1000000 elements in the array, the other I have process the last 1000000 elements in the array. My main thread waits for those two threads to notify it that they are finished, and then proceeds to use the values from array1 for all kinds of important stuff. (Note that the two worker threads may not be terminated and may be reused, but the main thread will not resume until they have both notified it to do so.)

So, my question is: How can I be sure that the main thread will see the modifications that the two worker threads made to the array? Can I count on this to happen or do I need to go through some special procedure to make sure the worker threads flush their writes to the array and the main thread discards its cached array values?

A: 

You will probably be fine if you are using callbacks when they are done modifying the arrays, but if there is any question using a Lock will ensure the other threads have let go of the arrays.

lock (array1)
{
}

http://msdn.microsoft.com/en-us/library/c5kehkcz(VS.71).aspx

sadboy
locking the entire array for the duration of the operation eliminates the ability of the threads to work in parallel.
Michael
It's also unneeded.
Reed Copsey
A lock ensures that no other thread has the array locked when a given thread enters it - it does not ensure that other threads are giving up their lock.
JoshJordan
Thanks for the correction, good to know.
sadboy
@JoshJordan Isn't that statement kind of contradictory, that it ensures none have a lock but doesn't ensure the others gave up their lock? I'll read up on it, but I guess i'm a little confused :)
sadboy
What I'm saying is that when you enter a lock, you are guaranteed to be the only thread operating within that lock's scope. However, it does not necessarily force any other thread to LEAVE a lock scope, so you may block indefinitely waiting for that to happen. It is not quite accurate to say that using a lock ensures other threads let go.
JoshJordan
+2  A: 

You need a memory barrier to ensure that the worker thread's writes to the array are visible to the main thread in the order you expect.

Whether or not you need an explicit memory barrier depends on how you notify the main thread. Waiting on most synchronization primitives, such as events, provide an implicit barrier, so no change would be required on your part. Polling a global variable does not provide a barrier.

If you need an explicit barrier, use Thread.MemoryBarrier.

Michael
Given that he's planning to block on the main thread (from the description), the memory barrier should be unnecessary.
Reed Copsey
He didn't explicity say how he was waiting (it could be a while(!done) {}). Any sane way of waiting will not require a barrier.
Michael
I'm using ManualResetEvent objects to let the main thread signal worker threads and vice versa.I've kind of been curious about MemoryBarrier's functionality as I've been working on this though. MS's docs say very little about it and basically just say that it prevents reordering of instructions.I got the impression that it flushed/cleared the current thread's cache, but never knew for sure. (If so, even without real thread synchronization, the workers could call MemoryBarrier and then if the main thread called MemoryBarrier afterward, it would see the changes to the array, right?)
nonoitall
So do ManualResetEvents qualify as "sane", not requiring a MemoryBarrier? :-) And is my understanding of MemoryBarrier accurate?
nonoitall
Yes, manual reset will work fine here.
Michael
A: 

As long as you didn't make a copy of the array in the main thread, I don't think you should have to do anything. Just wait until the worker threads are finished.

CodeSavvyGeek
+2  A: 

How can I be sure that the main thread will see the modifications that the two worker threads made to the array? Can I count on this to happen or do I need to go through some special procedure to make sure the worker threads flush their writes to the array and the main thread discards its cached array values?

You don't need any special handling here - you'll always be working off the same objects in memory.

Also, since each thread will work on a separate portion of the array, no locking is necessary.

However, if you're only doing a simple addition, the overhead of threading and synchronizing back to the main thread ~might~ outweigh the benefits gained... If you do this, profile to make sure that it's providing a net-gain.

Reed Copsey
Yeah - the actual operation is a bit more complex than addition (was just using that as an example) but it's the same in that the nth element of array1 is updated based on itself and the nth element of array2. Profiling on a decent-sized pair of arrays shows nearly twice the performance when going from one thread to two on a dual-core machine, so I'm pretty sure it's worth it. I just want to be sure I'm doing it correctly. :-)
nonoitall
@nonoitall: As long as you're always using the n-th element in a single thread at a single time, it should be no problem. Glad to hear that it's worth the effort!
Reed Copsey
+1  A: 

If you break up the index range into non-overlapping ranges as you suggested, then provided the array is created in shared memory (i.e. not by each thread), then no locks are required.

Mitch Wheat
+4  A: 

If you're lucky and have the option to use .NET 4.0 then you can just write:

Parallel.For(0, 2000000, i => { array1[i] += array2[i]; });

You don't need any explicit locking or synchronization, because:

  • Each task (execution of the for loops body) affects disjoint part of the array
  • Parallel.For waits until all tasks finish before it returns, so there will be an implicit memory barrier.
Tomas Petricek
That's awesome! I didn't realize they were adding that to .NET 4.0. Is it supported under Mono yet by any chance?
nonoitall
A qucik search shows that there are some attempts to support parallel stuff in .NET 4.0 on Mono: http://tirania.org/blog/archive/2008/Jul-26-1.html. I'm not sure what the current status is, though...
Tomas Petricek
I tried it out but it seems Parallel.For only performs slightly faster than using a single thread. Since my "addition" operation is pretty quick, I'm assuming the unimpressive speed comes from the hidden overhead of calling a delegate for every loop iteration. Too bad the CLR can't inline the delegate at runtime. I suppose I could unroll the loop to speed it up, but I think I'll stick to the manual method for now.
nonoitall