Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to synchronize the handover of array between 2 pool threads?

I have such scenario (this is Java pseudo code):

There is a main thread which:

1) creates an instance of an array of type C:

C[] arr = new C[LARGE];

2) creates and submits tasks which populate (by doing CPU bound operations) the arr to a pool P1:

for (int i = 0; i < populateThreadCount; i++) {
  p1.submit(new PopulateTask(arr, start, end))
}

Each task populates different range of indexes in arr so at this point synchronization is not needed between threads in pool P1.

3) the main thread waits until all populate tasks are finished.

4) once the arr is populated, main thread creates and submits tasks which upload (IO bound operations) the content of arr, to a pool P2:

for (int i = 0; i < uploadThreadCount; i++) {
  p2.submit(new UploadTask(arr, start, end);
}

As previously, the ranges are not overlapping, each thread has it's own range so no internal synchronization between threads in P2 pool is necessary.

In the populate and upload tasks the ranges are different as there is a different number of threads to handle each type.

Now I am thinking what is the most efficient way to synchronize it.

Using CopyOnWriteArrayList is not an option as it can be very large (millions of elements).

My initial idea was to synchronize briefly in a populate task after creating an instance of a C class and then to the same in an upload task:

C[] arr = new C[LARGE];

for (int i = 0; i < populateThreadCount; i++) {
 p1.submit(new PopulateTask(arr, start, end) {

  void run() {
   for (int j = start; j <= end; j++) {
    ... do some heavy computation ...
    arr[j] = new C(some_computed_data);
    synchronized(arr[j]) {}
   }
  }

});
}

for (int i = 0; i < uploadThreadCount; i++) {
 p2.submit(new UploadTask(arr, start, end) {

  void run() {
   for (int j = start; j <= end; j++) {
    synchronized(arr[j]) {
     upload(arr[j]);
    }
   }
  }

 });
}

but not sure if this is correct, especially if this empty synchronized block won't be optimized out by javac or JIT. I cannot create the instances of C class before starting the populate tasks as for that I need the computed data.

Any ideas, if that's correct and if not a way to do it better?

like image 760
Janek Avatar asked Apr 21 '15 19:04

Janek


1 Answers

You don't need to synchronize anything. The executor offers the memory visibility guarantees you need. In particular, see the concurrent package documentation:

  • Actions in a thread prior to the submission of a Runnable to an Executor happen-before its execution begins. Similarly for Callables submitted to an ExecutorService.
  • Actions taken by the asynchronous computation represented by a Future happen-before actions subsequent to the retrieval of the result via Future.get() in another thread.

So, the changes done by the tasks submitted to the first executor happen before what the main thread does after the executor has finished executing them (second rule), and what the main thread does with the array happens before the actions executed by the tasks submitted to the second executor (first rule).

Since happen-before is transitive, the tasks submitted to the second executor will see the changes made by the tasks submitted to the first one.

like image 176
JB Nizet Avatar answered Sep 26 '22 11:09

JB Nizet