Threads synchronisieren

Hallo,

Ich arbeite gerade an einem kleinen Projekt. Ziel ist es bei jedem durchlauf, WorkerThreads zu starten, darauf zu warten bis die Jobs ausgeführt wurden und dann die Hauptschleife fortzusetzten.
Leider kann ich keine Streams verweden und die Threads müssen immer die selben bleiben.

Meine derzeitige lösung nutzt 2 CyclicBarriers zur synchronisation. Leider geht dabei großteil der Zeit fürs Synchronisieren selbts drauf :frowning:

Hat jemand eine Idee wie man das ganze schneller machen könnte?

Hier ein KSGB:

import java.util.ArrayList;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * Created by michael on 29.01.15.
 */
public class ThreadTest {

    public static void main(String[] args) {
        new ThreadTest().start();
    }


    ArrayList<WorkerThread> workerThreads;
    CyclicBarrier entryBarrier, exitBarrier;


    long next;
    int count;

    private void start() {
        workerThreads = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            workerThreads.add(new WorkerThread(this));
        }
        entryBarrier = new CyclicBarrier(workerThreads.size() + 1);
        exitBarrier = new CyclicBarrier(workerThreads.size() + 1);
        workerThreads.forEach(t -> new Thread(t).start());

        new Thread(() -> {
            while (true) {
                launchWorkerThreads();
                if (next < System.nanoTime()) {
                    System.out.println(count);
                    count = 0;
                    next = System.nanoTime() + 1000000000;
                } else {
                    count++;
                }
            }
        }).start();
    }

    private void launchWorkerThreads() {
        try {
            entryBarrier.await();
            exitBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }

    private class WorkerThread implements Runnable {
        ThreadTest threadTest;

        public WorkerThread(ThreadTest threadTest) {
            this.threadTest = threadTest;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    threadTest.entryBarrier.await();
                    doJob();
                    threadTest.exitBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }

            }
        }

        private void doJob() {

        }
    }
}

Auch wenn ich selbst bisher noch nicht all zu viel mit Multi-Thread-Sync zu tun hatte frag ich trotzdem mal :

Du baust im main-Thread WorkerThreads zusammen damit diese halt die Arbeit parallel abarbeiten, willst aber am Ende doch darauf warten bis diese (alle ?) komplett sind um dann weiter zu machen ?
Spontan würde mir wait(), join() und notify() einfallen, weis aber nicht ob dies deinen Anforderungen genügt.

        new Thread(() -> {
            while (true) {
                launchWorkerThreads();
                if (next < System.nanoTime()) {
                    System.out.println(count);
                    count = 0;
                    next = System.nanoTime() + 1000000000;
                } else {
                    count++;
                }
            }
        }).start();

Was das Kontrukt machen soll ist mir zugegebenener Maßen nicht ganz klar. Irgendeine komische Form der Zeitmessung?

Außerdem: Warum müssen das die gleichen Threads bleiben?
Falls du die Threads durchlaufen lässt, kannst du mit join() auf das Ende der Threads warten.

So komisch ist das Kostrukt doch gar nicht… ein schneller weg um die anzahl der durchläufe pro Sekunde zu bekommen :slight_smile:

Ja, ich muss darauf warten dass alle Jobs abgeschlossen sind, das die nechste Runde auf dem Ergebnis der vorigen beruht.

Threads müssen bestehen bleiben da ich „Thread local“ (nennt man das so?) Variablen benutze. OpenGL um genau zu sein. Jeder Thread hat seinen eigenen Context und rendert drauf los.

[quote=CCMike]Threads müssen bestehen bleiben da ich “Thread local” (nennt man das so?) Variablen benutze.[/quote]Da würde es aber reichen, wenn die Runnables die selben bleiben und immer neuen Threads übergeben werden.

bye
TT

Das würde mir einiges an “Arbeit” sparen, werds mal schnell testen

*** Edit ***

Nope, sieht so aus als würde der GLContext an den Thread gebunden…

Ja, GL ist üblicherweise an den Thread gebunden.
Also du hast einen GL-Thread. Und du willst Aufgaben schedulen, die irgendwie “mit dem GL-Thread syncrhonisiert” sind, aber NICHT vom GL-Thread, sondern parallel von mehreren anderen ausgeführt werden?

FALLS das richtig ist, denke ich, dass da ein ExecutorService schon passen könnte. GROB nach dem Muster

class Renderer {
    private ExecutorService executorService; // Z.B. fixed thread pool
    private List<Runnable> tasks;

    void scheduleTask(Runnable task) { tasks.add(task); }

    void thisIsExecutedInGLThread() {

        // Blockiert, bis alle Tasks abgearbeitet sind
        executorService.invokeAll(tasks);

        tasks.clear();

        // Render stuff....
        ....
    }
}

(natürlich mit ein paar try/catch dabei, und ggf. synchronisation der task-Liste, aber vom Ansatz her…!?)

Hallo,

Eigentlich genau umgekehrt. Die “WorkerThreads” sind alles GL-Threads mit Context Sharing. Der Hautthread bereitet Listen von gl commands vor welche dann von den workern abgearbeitet werden. Funktioniert soweit auch alles wie geplant. Bis auf das kleine Performanceproblem mit dem Synchronisieren. Leider ist das binden von den Contexts auch recht teuer. Also ein makeContextCurrent() am anfang jedes durchlaufs kommt nicht in Frage.

Werde mal versuchen mir einen eigenen Executor nachzubauen welcher immer den selben Thread einem Callable zuweist.

Ich müßte das wohl nochmal detaillierter nachvollziehen, aber … man kann einem ExecutorService auch eine eigene Thread-Factory übergeben, die hier dann ggf. die vorgefertigten GL-Threads lieferrn könnte (kann aber sein, dass das auch nicht passt… nochmal genauer lesen)

Mit einem schnell zusammen gewürfelten Executor Service komm ich auf 1.25ms pro durchlauf. (Die CyclicBarrier Lösung brauchte ca 9.09ms). Immer bei 100 Threads. Ich denke mit der neuen Methode kann ich vorläufig leben. Auch vereinfacht es mein Design enorm.