kopia lustrzana https://github.com/onthegomap/planetiler
119 wiersze
3.3 KiB
Java
119 wiersze
3.3 KiB
Java
package com.onthegomap.planetiler.worker;
|
|
|
|
import java.util.concurrent.ArrayBlockingQueue;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.function.Consumer;
|
|
import javax.annotation.concurrent.NotThreadSafe;
|
|
import javax.annotation.concurrent.ThreadSafe;
|
|
|
|
/**
|
|
* Redistributes work among worker threads when some finish early.
|
|
* <p>
|
|
* When a group of worker threads are processing large blocks, some may finish early, resulting in idle time at the end
|
|
* waiting for the "long pole in the tent" to finish:
|
|
*
|
|
* <pre>
|
|
* {@code
|
|
* busy idle | done
|
|
* worker1: ===========>xxxxxx|
|
|
* worker2: ===============>xx|
|
|
* worker3: =================>|
|
|
* worker4: =============>xxxx|
|
|
* }
|
|
* </pre>
|
|
* <p>
|
|
* This utility wraps the operation to perform on each element and then works through items in 3 phases:
|
|
*
|
|
* <ol>
|
|
* <li>If all threads are still busy, process it in the same thread</li>
|
|
* <li>If some threads are done, enqueue the item onto a work queue (but if it is full, just process it in the same
|
|
* thread)</li>
|
|
* <li>When the thread is done processing input elements, then process items off of the work queue until it is empty and
|
|
* all other workers are finished</li>
|
|
* </ol>
|
|
*
|
|
* @param <T> The type of element being processed
|
|
*/
|
|
@ThreadSafe
|
|
public class Distributor<T> {
|
|
|
|
private final AtomicInteger done = new AtomicInteger();
|
|
private final AtomicInteger working = new AtomicInteger();
|
|
private final ArrayBlockingQueue<T> pending;
|
|
|
|
private Distributor(int capacity) {
|
|
pending = new ArrayBlockingQueue<>(capacity);
|
|
}
|
|
|
|
/** Returns a new {@code Distributor} that can hold up to {@code capacity} pending elements. */
|
|
public static <T> Distributor<T> createWithCapacity(int capacity) {
|
|
return new Distributor<>(capacity);
|
|
}
|
|
|
|
/** A handle for each worker thread to offer new items, and drain the remaining ones when done. */
|
|
@NotThreadSafe
|
|
public interface ForThread<T> extends Consumer<T>, AutoCloseable {
|
|
|
|
void finish();
|
|
|
|
void drain();
|
|
|
|
@Override
|
|
void close();
|
|
}
|
|
|
|
public ForThread<T> forThread(Consumer<T> consumer) {
|
|
working.incrementAndGet();
|
|
return new ForThread<>() {
|
|
boolean finished = false;
|
|
|
|
@Override
|
|
public void accept(T t) {
|
|
if (finished) {
|
|
throw new IllegalStateException("Finished");
|
|
}
|
|
if (done.get() == 0 || !pending.offer(t)) {
|
|
consumer.accept(t);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void finish() {
|
|
if (!finished) {
|
|
done.incrementAndGet();
|
|
working.decrementAndGet();
|
|
finished = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void drain() {
|
|
T item;
|
|
while ((item = pending.poll()) != null || working.get() > 0) {
|
|
if (item == null) {
|
|
try {
|
|
item = pending.poll(100, TimeUnit.MILLISECONDS);
|
|
if (item == null && working.get() <= 0) {
|
|
break;
|
|
}
|
|
} catch (InterruptedException e) {
|
|
Thread.currentThread().interrupt();
|
|
break;
|
|
}
|
|
}
|
|
if (item != null) {
|
|
consumer.accept(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
finish();
|
|
drain();
|
|
}
|
|
};
|
|
}
|
|
}
|