13

If each thread is guaranteed to only read/write to a specific subset of the array can multiple threads work on the same (static) array without resorting to critical sections, etc?

EDIT - This is for the specific case of arrays of non-reference-counted types and record/packed-records thereof.

If yes, any caveats?

My gut feeling is yes but my gut can sometimes be an unreliable source of information.

J...
  • 30,968
  • 6
  • 66
  • 143
  • OK, I know what that means now. I'd call that fixed size but there you go. It makes no difference whether or not the size is fixed. It makes no difference whether or not the array is of global, local or class scope. It makes no difference whether or not the array is heap allocated or stack allocated. It's just a contiguous array of items. – David Heffernan Apr 16 '12 at 13:55
  • No, my mistake. I'd never known that usage. I guess it must have been introduced after I learnt Delphi at the time that dynamic arrays were added, since all arrays before then were static. In any case, static or not makes no difference at all. – David Heffernan Apr 16 '12 at 14:03
  • Fair enough. As you say, in any case, there doesn't seem to be an obvious reason why concurrent access should be a problem but sometimes there are dark subtleties which confound such things. Sometimes it is nice to be doubly certain. – J... Apr 16 '12 at 14:09
  • @DavidHeffernan There is a difference, IMHO. Static arrays are allocated and copied when read (unless they are passed by reference, of course), whereas dynamic arrays are reference counted and have copy-on-write patterns, which may make a difference. It is (more or less) similar to the `shortstring / string` use patterns. In all cases, static arrays may not be thread-safe, in some situations: see my answer. – Arnaud Bouchez Apr 16 '12 at 14:12
  • @ArnaudBouchez That's a rather bizarre interpretation of "on the same (static) array". I take **the same** as meaning, well, the same single instance of an array. So, there's no copying here. – David Heffernan Apr 16 '12 at 14:17
  • @DavidHeffernan Copying may occur when you pass the data to another sub function, e.g. – Arnaud Bouchez Apr 16 '12 at 14:59
  • @ArnaudBouchez Yes copying may occur then. But the question makes it clear that copying does not occur. – David Heffernan Apr 16 '12 at 15:01
  • @DavidHeffernan - yes, no copying. All parties get a pointer to the array and an offset range to call their own. – J... Apr 17 '12 at 09:21

2 Answers2

9

Suppose that:

  1. You have a single instance of an array (static or dynamic), and
  2. The elements of the array are pure value types (i.e. contain no references), and
  3. Each thread operates on disjoint sub-arrays, and
  4. Nothing else in the system writes to the array whilst the threads are operating on it.

With these conditions, which I believe are met by your data structure and threading pattern, then all algorithms are thread-safe.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I presume this would also be the case with reference type objects IF references to the same object are guaranteed to only exist within a single sub-array (ie: no common references shared between disjoint sub-arrays). – J... Apr 16 '12 at 14:56
  • 2
    @J... Yes that is correct. So long as each thread operates on data private to that thread, then algorithms are thread-safe. – David Heffernan Apr 16 '12 at 14:58
  • @DavidHeffeman, Is condition#4 really necessary? What is the difference of the main thread or another process writing to the array to the threads operating on it? – Arnold Apr 16 '12 at 17:20
  • @arnold er, condition 4 certainly is necessary. – David Heffernan Apr 16 '12 at 17:43
  • @DavidHeffernan - What do you mean for disjoint sub-arrays? Slices of the "main" array, distributed in some way to each thread so they don't access each other's slice? – Leonardo Herrera Apr 16 '12 at 20:42
  • @leonardo that's right. And disjoint means that the slices do not overlap each other. – David Heffernan Apr 16 '12 at 20:44
  • @DavidHeffernan - thank you. I think that the OP needs to be aware that the process of distributing slices to each thread there may be some concurrency issues to be avoided, too. – Leonardo Herrera Apr 16 '12 at 20:49
  • @LeonardoHerrera - the array in question will not be divided into new sub-arrays, if that's what you are thinking. Application logic alone will define the ranges of array indices permissible for each thread to access. Consider `arr1 = array[0..99] of double` and threads `0-9` where the n'th thread will work exclusively on array elements 10n->10n+9. – J... Apr 16 '12 at 20:59
  • @J... - I wasn't thinking in actually dividing the array. I understand that each thread will be assigned its own slice. I just wanted to point that if that assignment could occur in a thread (for example, worker threads assigning slices by themselves) then you still need to protect that part. But I understand that if this assignment is done on the main thread then it is not an issue. Gawd, just talking about threads is convoluted. – Leonardo Herrera Apr 17 '12 at 14:30
8

No, this could not be thread safe, in some situations.

I see at least two reasons.

1. It will depend on the static array content.

If you use some non-reference counted types (like double, integer, bytes, shortstring), there won't be any issue in most case (at least if data is read/only).

But if you use some reference-counted types (like string, interface, or a nested dynamic array), you'll have to take care of thread safety.

That is:

TMyType1: array[0..1] of integer; // thread-safe on reading
TMyType2: array[0..1] of string;  // may be confusing

Additional note: if your string is in fact shared among some sub-parts of the static array, you could have the reference count be confused. Unless you explicitly call UniqueString() for each one (inside a critical section, I suspect). For an array of double or integer, you won't have this issue.

2. It will depend on the access concurrency

Read access should be thread safe, even for reference counted type, but concurrent write may be confusing. For a string, you may have GPF issues in some random cases, especially on a multi-core CPU.

Some safe implementation may be:

  • Use critical sections (smaller as possible, to reduce overhead) or other protection structures;
  • Use Copy-On-Write or a private per-thread copy of the content, to be sure;
  • Latest note (not about safety, but performance): Sharing an array among multiple CPUs may lead into performance penalties due to cache synchronization between CPUs. Performance is sometimes much better when you use separated arrays, ensuring their L1 caching window won't be shared among CPUs.

Be aware that such issues may be a nightmare to debug, on client side: multi-thread concurrency issues may occur randomly, and are very difficult to track. The safer, the better, unless you have explicit and proven performance issues.

Additional note: For your specific case of static array of double, with sub-part of the array accessed by one thread only, it is thread-safe. But there is no absolute rule of thread safeness in all situations, even for a static array. As soon as you use some reference-counted types, or some pointers, you may have random issues.

Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • Good point - I should have been specific about the array type. In this case the elements are packed records of doubles (which I think should be safe). – J... Apr 16 '12 at 14:15
  • This answer is simply wrong in my view. The thrust of the question is that each thread operates on disjoint sub-arrays. And that only thread that owns each sub-array operates on it. That is thread-safe no matter what the element type is. – David Heffernan Apr 16 '12 at 14:21
  • @DavidHeffernan - also a good point. Arrays of reference counted objects hold pointers only, yes? – J... Apr 16 '12 at 14:30
  • @J... Yes... and no: they contain a pointer to some content, precessed by a reference count! The thread-safe issue is about reference count handling. Even if reference count access use an atomic low-level asm instruction, on multiple threads, you can have some race condition in case of concurrent update. For reading, it will be OK. – Arnaud Bouchez Apr 16 '12 at 14:33
  • @J... If each thread operates on its own private sub-array then it makes no difference at all what's in the array. OK, the exception to that is if distinct sub-arrays contain references to a common object. – David Heffernan Apr 16 '12 at 14:35
  • @DavidHeffernan As I added in my answer, you may have issues with disjoint sub-arrays, if e.g. some `string` are shared in those arrays - which may occur with `string`, unless you explicitly call `UniqueString()`. – Arnaud Bouchez Apr 16 '12 at 14:38
  • My point is that so long as the underlying data is not shared between threads then all algorithms are thread safe. Your answer would be better if it stated this up front because in fact that's exactly the situation that @J... is asking about. – David Heffernan Apr 16 '12 at 14:46
  • @DavidHeffernan Reference-counted variables may be shared between threads, even if they are accessed via diverse sub arrays, since they are pointers to some shared content. That is, two threads may access to the same reference count from two diverse sides, at once. This is *indirect* shared data, but it may occur from another layer (e.g. UI). I've edited the answer to reflect this. – Arnaud Bouchez Apr 16 '12 at 14:58