7

Possible Duplicate:
Are C# arrays thread safe?

I have been programming C# for 10 months now. I am now learning multi-threading and seems to be working nicely. I have multi-dimension array like

string[,] test = new string[5, 13]; 

I have the threads calling methods that end up saving their outputs to different coordinates inside the array above. without any one thread writing to the same location as another thread.

So thread1 might write to test[1,10] but no other thread will ever write to test[1,10]

My question is: I have read about using locks on objects like my array, do I have to worry at all about locks even though my threads might access to the test array at the same time but never write to the same coordinates(memory location)?

So far in my testing I have not had any issues, but if someone more seasoned than myself knows I could have issue then I'll look into use locks.

Community
  • 1
  • 1
john johnson
  • 699
  • 1
  • 12
  • 34
  • 1
    No, you don’t need locks in the explained scenario. But I assume that you are reading the data at some point? Do you wait until all your threads have finished before starting to read? – Douglas Feb 09 '12 at 21:16
  • 1
    If you don’t use locks, it would be recommendable to introduce a memory barrier (by calling `Thread.MemoryBarrier()`) before commencing to read, in order to ensure that all threads have access to ‘fresh’ data. [Nonblocking Synchronization](http://www.albahari.com/threading/part4.aspx) has a great explanation about this concept. – Douglas Feb 09 '12 at 21:24
  • thanks, the threads are spawned from a single master thread, they only write data to the array, then once they all finish, the RunWorkerCompleted off the master thread, reads the array. so i think i am safe, thanks for the explanation. – john johnson Feb 09 '12 at 21:32
  • @Douglas How can memory barrier help here??? The order of array elements changing does not matter. I.e. test[1,10] does not need to be "fresh" when test[2,10] is writing. – Dims Feb 09 '12 at 21:42
  • @Dims: I said that the memory barrier needs to be effected *before commencing to read*. Otherwise, a reader thread might have a stale version of the data (e.g. an array whose values are still initialized to `null`). – Douglas Feb 09 '12 at 21:49
  • @johnjohnson: I’m not sure whether `RunWorkerCompleted` implicitly introduces a memory barrier (although I would guess that it does). If it doesn’t, it might be recommendable to introduce a `Thread.MemoryBarrier()` call at the beginning of its assigned delegate. – Douglas Feb 09 '12 at 21:54
  • 3
    @Dims: Is your supposition that the array is *never going to be read from*? If the array is never going to be read from, then why write to it? If the array *is* going to be read from, *what causes the reading thread's processor cache to synchronize with main memory*? And what causes the *writing* thread's processor caches to synchronize with main memory? – Eric Lippert Feb 09 '12 at 21:57
  • @Douglas and Eric thanks, understood. But is it possible for memory writes be delayed after thread was already terminated? I suppose this is needed only if thread is waiting which is not the case. – Dims Feb 10 '12 at 07:56
  • @Dims: Yes, it is possible. In a multi-processor system, each processor would have its own memory cache, whose contents may be transiently different from main memory, even if storing the same C# variable (so to speak). – Douglas Feb 10 '12 at 09:45
  • 1
    @Dims: Suppose Thread A is executing on Processor 1; any data it writes may be written to that processor’s cache instead of directly to main memory. Suppose that, later, Thread B is executing on Processor 2. This processor’s cache does not have the updated values that were written to the cache of Processor 1, even if Thread A is no longer executing there. A “memory barrier” is required to synchronize the contents of all processor caches with main memory, thereby ensuring they have a consistent view of the values of any variables they reference. – Douglas Feb 10 '12 at 09:47
  • This is an advanced topic; I would suggest reading the link I pasted above if you seek a better understanding. – Douglas Feb 10 '12 at 09:48
  • @Douglas Can you please provide some examples of the systems where one thread waiting for another termination does not automatically uses memory barrier? Here http://stackoverflow.com/questions/9224542/is-a-memory-barrier-required-if-a-second-thread-waits-for-termination-of-the-fir – Dims Feb 10 '12 at 10:05
  • @Dims: I mentioned [below](http://stackoverflow.com/a/9218892/1149773) that most signaling constructs implicitly introduce memory barriers. However, one might not be using a signaling construct for waiting. The simplest scenario: waiting by polling on a volatile sentinel flag. (`volatile` guarantees that the value of that specific variable is up-to-date across all processors, but it does not extend that guarantee to other variables that are (logically) related to it.) – Douglas Feb 10 '12 at 10:31
  • @Douglas: To clarify your comment: a volatile operation provides a *half fence* in C#; see the specification for details. Volatile reads are guaranteed to be up-to-date, but C# does not promise that a *consistent ordering* of reads and writes is observed from all threads. – Eric Lippert Feb 10 '12 at 14:24
  • @EricLippert: You’re right, I’d forgotten that detail; thanks! – Douglas Feb 10 '12 at 18:42

4 Answers4

9

If you can ensure that no two threads will ever attempt to read or write to the same element then you have no need to do any locking, and if you do add locking you will slow down your code.

You should however take the time to add comments as appropriate to explain that, (and possibly why) no threads ever access the same elements to avoid future problems that Jim Fell has mentioned in his answer.

Update: Many of the other posters are continuing to suggest that locking be used merely as a safeguard against mistakes by future developers. To that end, it really depends on your application. If performance really isn't much of an issue, then sure, go ahead and synchronize access to the elements.

However, most of the time in which multiple threads are accessing a single array the reason that multiple threads exist is to perform parallel processing on large amounts of data in which performance is a significant concern. If it wasn't much of a concern, then you could just use a single thread and be far more at ease about it not being messed up by others. In such high performance computing reliance on lock (in various forms) is, as a rule, minimized whenever possible. Synchronization through separation of data (meaning preventing them from ever reading/writing to the same memory locations) is far superior to using locks, when it is feasible.

Servy
  • 202,030
  • 26
  • 332
  • 449
1

One thing to be careful about is if you are testing with a single core processor, everything might work fine, but as soon as you run on a multi-core processor, you run into problems with threads hitting a shared piece of memory at the same time.

Just to be safe, if the two threads are actually modifying your multi-array directly, you need to implement a locking system. This answer in StackOverflow has a good example of locking.

Community
  • 1
  • 1
urbadave
  • 231
  • 1
  • 3
  • 9
0

You should be using lock statements to ensure that your data object is thread-safe. It might be working now, but later (especially when/if the code is updated) you (or your replacment) could run into elusive race-condition or data-corruption bugs. Check out this article on the MSDN website. It has a nice example on how to use lock statements.

Jim Fell
  • 13,750
  • 36
  • 127
  • 202
-1

Locks are only for preventing simultaneous access. So if you guarantee it in other way, you need no locks.

P.S. I suppose you have assigned each thread to each array element, right?

Dims
  • 47,675
  • 117
  • 331
  • 600
  • 4
    Simultaneous access is only an issue if the simultaneous operations are both *mutating* and *non-atomic*. But locks do more than make non-atomic operations into atomic operations; they also introduce *full-fence memory barriers*. Suppose the author of the code cleverly guarantees that no two non-atomic reads/writes of memory will be interleaved; that's great, but doing so may be insufficient. **Doing so is not a guarantee that any two threads have a *consistent* view of all mutations to all the data**. – Eric Lippert Feb 09 '12 at 21:19
  • He told each thread works with it's own array element. The ordering does not matter here. – Dims Feb 09 '12 at 21:40
  • 2
    You're not following my point. Suppose thread Alpha writes 123 to array location 2. Suppose thread Bravo is guaranteed to not read location 2 until after thread Alpha is done the mutation, via some non-locking solution. What guarantee is there that when thread Bravo reads location 2, that it finds 123 in that location? None whatsoever! Thread Bravo could be running on a different processor than thread Alpha, and **Bravo's processor could have a cached the memory page of the array.** What forces Bravo's CPU to invalidate the cache? *Nothing*. – Eric Lippert Feb 09 '12 at 21:52
  • @EricLippert: Would you happen to know whether `BackgroundWorker.RunWorkerCompleted` implicitly introduces a memory barrier? – Douglas Feb 09 '12 at 21:58
  • @Douglas: No idea, sorry. It would certainly be necessary to find out in order to know whether the operation described in the question is safe or not. – Eric Lippert Feb 09 '12 at 22:00
  • Ok, thanks anyway. Agreed (although the implicit barrier might also be effected by signaling constructs, such as `Task.Wait`, which I assume is being used for synchronizing the completion of the worker threads). – Douglas Feb 09 '12 at 22:13
  • > What guarantee is there that when thread Bravo reads location 2, that it finds 123 in that location? @Eric John guaranteed! he said Bravo will NEVER read location 2 since it belongs to Alpha! – Dims Feb 10 '12 at 07:47
  • @Dims: No, that's not the scenario. The scenario is one thread -- the "main" thread -- spawns a lot of worker threads. Each worker thread is assigned a portion of the array to write. When all the worker threads are done, the main thread then reads the result. **What mechanism ensures that the main thread's view of memory is consistent with that of *all* the workers**? – Eric Lippert Feb 10 '12 at 14:21
  • My point is simply that your statement in your answer is incomplete. The statement should be "Locks prevent simultaneous access **and induce memory fences**. If you guarantee non-simultaneous access **and guarantee an appropriate fence** in some other way then the lock is not necessary." – Eric Lippert Feb 10 '12 at 14:28
  • @Eric, I agreed. It was interesting to know about barriers, thanks. – Dims Feb 10 '12 at 16:58