2015-05-22 6 views
5

Ten mały fragment kodu nigdy nie kończy się na jdk8u45 i stosowane prawidłowo zakończyć na jdk8u20:ForkJoinPool, Phaser i zarządzane blokowanie: w jakim stopniu działają one przeciwko impasom?

public class TestForkJoinPool { 

    final static ExecutorService pool = Executors.newWorkStealingPool(8); 
    private static volatile long consumedCPU = System.nanoTime(); 

    public static void main(String[] args) throws InterruptedException { 
     final int numParties = 100; 
     final Phaser p = new Phaser(1); 
     final Runnable r =() -> { 
      p.register(); 
      p.arriveAndAwaitAdvance(); 
      p.arriveAndDeregister(); 
     }; 

     for (int i = 0; i < numParties; ++i) { 
      consumeCPU(1000000); 
      pool.submit(r); 
     } 

     while (p.getArrivedParties() != numParties) {} 
    } 

    static void consumeCPU(long tokens) { 
     // Taken from JMH blackhole 
     long t = consumedCPU; 
     for (long i = tokens; i > 0; i--) { 
      t += (t * 0x5DEECE66DL + 0xBL + i) & (0xFFFFFFFFFFFFL); 
     } 
     if (t == 42) { 
      consumedCPU += t; 
     } 
    } 
} 

W doc of phaser członkowskich, które

Fazery mogą być również wykorzystywane przez zadań realizujących w ForkJoinPool, który będzie zapewnić wystarczającą paralelność, aby wykonywać zadania, gdy inni są zablokowani, czekając na fazę, aby przejść dalej.

jednak javadoc of ForkjoinPool#mangedBlock stany:

Jeśli pracuje w ForkJoinPool, pula może najpierw zostać rozszerzony, aby zapewnić wystarczającą równoległość

tylko może. Nie jestem więc pewien, czy jest to błąd, czy tylko zły kod, który nie polega na umowie Phaser/ForkJoinPool: jak trudna jest umowa połączenia Phaser/ForkJoinPool, aby zapobiec zakleszczeniom?


Mój config:

  1. Linux ADC 3.14.27-100.fc19.x86_64 # 1 SMP Wed 17 grudnia 2014 19:36:34 UTC x86_64 x86_64 x86_64 GNU/Linux
  2. 8 rdzenie i7
+2

Zapisano: https://bugs.openjdk.java.net/browse/JDK-8080939 –

+0

OK, dzięki. –

+1

@AlekseyShipilev Może więcej informacji na temat problemu: Jeśli usunę część comsumeCpu() lub zmniejszę liczbę tokenów, test zakończy się poprawnie. To samo, jeśli przerwę w ForkJoinPool # tryCompensate i krok ręcznie –

Odpowiedz

3

Wygląda na to, że Twój problem wynika ze zmiany kodu ForkJoinPool między JDK 8u20 a 8u45.

W u20 wątki ForkJoin były zawsze aktywne przez co najmniej 200 milisekund (patrz forkJoinPool.FAST_IDLE_TIMEOUT), zanim zostaną odzyskane.

W u45, gdy ForkJoinPool osiągnie docelowy paralelizm plus 2 dodatkowe wątki, wątki umrą, gdy tylko stracą pracę bez czekania. Można zobaczyć tę zmianę w sposobie awaitWork w ForkJoinPool.java (linia 1810):

int t = (short)(c >>> TC_SHIFT); // shrink excess spares 
    if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl)) 
     return false; 

Program wykorzystuje zadania Fazery do tworzenia dodatkowych pracowników. Każde zadanie spawnuje nowego pracownika kompensującego, który ma odebrać następne przesłane zadanie.
Jednak po osiągnięciu docelowego równoległości + 2 pracownik rekompensujący umiera natychmiast, nie czekając i nie ma szansy na odebranie zadania, które zostanie przesłane zaraz po nim.

Mam nadzieję, że to pomoże.

Powiązane problemy