5

C# ensures that certain types always have atomic reads and writes. Do I have those same assurances when calling Array.Copy on two arrays of those types? Is each element atomically read and written? I browsed some of the source code but did not come away with a solid answer.

For example, if I rolled my own code for copying two arrays...

static void Copy<T>(T[] source, T[] destination, int length)
{
    for (int i = 0; i < length; ++i)
        destination[i] = source[i];
}

... and called the Copy<int> variant, this guarantees that each element is atomically read from source and atomically written to destination because C# promises that int reads and writes are atomic. I'm simply asking if Array.Copy maintains that guarantee (as opposed to, say, using its own specialized memory block copy routine that possibly breaks this guarantee).

TheBuzzSaw
  • 8,648
  • 5
  • 39
  • 58
  • What are you trying to do? – spender Jul 14 '18 at 20:43
  • 1
    Yes, it's the same. – Matthew Watson Jul 14 '18 at 20:58
  • 1
    Just to be clear: non-atomic-copy types (e.g. Decimal) are not threadsafe - you could get torn reads if one thread is reading an array that you are overwriting using `Array.Copy()`. But atomic-copy types (e.g. `int` and reference types) are threadsafe. – Matthew Watson Jul 14 '18 at 21:13
  • @MatthewWatson That's what I figured. I _assumed_ that `Array.Copy` would be fine here. I was just hoping to walk away from this question with either a link to documentation or a link to source code to be able to confirm to others that it's fine. – TheBuzzSaw Jul 14 '18 at 21:17
  • 1
    I found some discussion about Array.Copy here: https://stackoverflow.com/questions/6558266/how-is-array-copy-implemented-in-c though to be honest that sounds like it wouldn't guarantee it to me – steve16351 Jul 14 '18 at 21:17
  • @steve16351 Yeah, that's what I gathered from that too. I totally agree with their decision to write a fast routine, but I wonder what that means in my situation where I want to grab a copy of an `int[]` while other threads are mutating it. I'm absolutely fine with whatever state my copy is in; I just don't want an individual value to glitch out. – TheBuzzSaw Jul 14 '18 at 21:24
  • 1
    The array's contents, as a whole provide no guarantees. If you grab a copy with other threads are mutating the contents, you may get half of the other thread's updates. But, if the individual elements provide atomic guarantees, then each element will not get torn apart during the copy – Flydog57 Jul 14 '18 at 21:30
  • @Flydog57 Yup. I'm fine with that. I was just looking for hard confirmation on that last part. :) – TheBuzzSaw Jul 14 '18 at 21:33
  • Here you go: https://stackoverflow.com/questions/11745440/what-operations-are-atomic-in-c – Flydog57 Jul 14 '18 at 23:08
  • @Flydog57 I... literally linked that in my question. – TheBuzzSaw Jul 14 '18 at 23:11
  • Oops, copied the wrong link. Working from my phone is a PITA – Flydog57 Jul 15 '18 at 00:44
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176001/discussion-between-thebuzzsaw-and-flydog57). – TheBuzzSaw Jul 15 '18 at 04:42

1 Answers1

10

Array.Copy() tries to make the copy efficient by using the memmove() CRT function, a raw memory-to-memory copy without regard for the actual type stored in the array. It can be substantially more efficient if the array element type is smaller than the natural processor word size.

So you need to know whether memmove() can provide the atomicity guarantee. That is an tricky question that the CLR programmer answered unambiguously. Atomiticy is an essential trait for object references, the garbage collector cannot operate correctly when it can't update those references atomically. So the programmer special-cases this in the CLR code, the comment he provided tells you what you want to know (edited to fit):

// The CRT version of memmove does not always guarantee that updates of 
// aligned fields stay atomic (e.g. it is using "rep movsb" in some cases).
// Type safety guarantees and background GC scanning requires object 
// references in GC heap to be updated atomically.

That's a very pessimistic view on life. But clearly no, you can't assume Array.Copy() is atomic when the CLR author did not make this assumption.


Practical consideration do perhaps need to prevail. On reasonably common architectures, x86 and x64 have a memmove() implementation that don't make the CLR memory model guarantees worse, they copy 4 or 8 aligned bytes at a time. And practically the for-loop in the generic code substitute is not guaranteed to be atomic since T is not guaranteed to be.

Most practical is that you ought not have to ask the question. Atomicity only matters when you have another thread that is accessing the arrays without any synchronization. Writes to the source array or reads from the destination array. That is however a guaranteed threading race. Writes to the source array are worst, the copy has an arbitrary mix of old and new data. Reads from the destination array randomly produce stale data, like a threading bug normally does. You have to be quite courageous to risk this kind of code.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • In my case, I am OK without the synchronization. I was working on a ring buffer and creating a method that copied a snapshot of certain region. I am OK if values within the snapshot changed mid-copy, but I just wanted to avoid torn values. The arrays are typically `int` or reference types. – TheBuzzSaw Aug 18 '18 at 03:39