# Sehr viele Threads effizient Verwalten



## HalloWelt_ (29. Mai 2012)

Hallo.
Mein Problem ist folgendes: Ich arbeite an einem Programm, in dem der Benutzer sein eigenes Spiel oder ähnliches programmieren kann. Das Programm hat einzelne Grafische-Objekte welche wiederum beliebig viele Scripte enthalten. Ein Script kann zum Beispiel aus einer Endlosschleife bestehen, welche ununterbrochen auf Kollision testet. Nun ist das Problem, dass ich die Scripte nicht so einfach in "echten" 
	
	
	
	





```
java.lang.Thread
```
s ausführen kann, da dass vor allem bei der großen Anzahl von parallel laufender Scripte (bis zu 100) sehr kostspielig ist und es nur noch hängt (die Benutzeroberfläche reagiert so gut wie überhaupt nicht). Im Moment emuliere ich das Multithreading was aber kompliziert und nicht sehr schnell ist (+ tagelanges debuggen). Gibt es eine Alternative dafür? Ich dachte daran, den 
	
	
	
	





```
java.lang.Thread
```
s eine  sehr kleine Stack Größe zu zu weißen und die Priorität massiv einzuschränken. (Ich weiß, es gibt einen Parameter für die JVM um die Stack Größe zu setzen. Der würde dann aber auch die echten/wichtigen Threads meiner Anwendung manipulieren.). Gibt es eine alternative Threading-API welche für mein Vorhaben geeignet ist?

Sehr wahrscheinlich werde ich um das Fehler suchen in meinem emuliertem Multithreading nicht herum kommen, aber für Tipps bin ich dankbar!

Viele Grüße,
HalloWelt_


----------



## Cola_Colin (29. Mai 2012)

Ein Skript sollte einfach gar keinen eigenen Thread spawnen, wenn es soviele Skripte gibt.
Eher sollte ein Skript z.B. eine Methode doTick(int dt) implementieren, die nacheinander für alle Skripte aufgerufen wird, mit dt = der vergangenen Zeit in ms. In dieser Methode kann der Skript dann tun, was auch immer er will.
Aus dem Thread der GUI würde ich das ganze sowieso raushalten.


----------



## HalloWelt_ (30. Mai 2012)

Ich habe nicht gemeint, dass ich derzeitig meine Scripts im GUI-Thread ausführe. Um Gottes Willen, was traust du mir den zu . Ich meinte, dass wenn man über 100 Threads laufen hat kommt der GUI-Thread nicht mehr zum Zuge. 
Das mit dem 
	
	
	
	





```
doTick(int dl)
```
 kann ich auch nicht umsetzen: Der Benutzer kann die Skripte so aufbauen wie er will und spätestens wenn 
	
	
	
	





```
doTick(int dl)
```
 in seiner Zeit nicht fertig wird, wird es den Stack sichern müssen, damit es beim nächsten Tick weiter arbeiten kann. So etwas ähnliches habe ich auch schon implementiert, und es klappt auch, aber es ist sehr kompliziert und hat einen großen Overhead.

Dies ist der wichtigste Teil meines Thread-Emulators und man kann den Overhead schon beim hinschauen erkennen.
Ich habe absichtlich auf getter/setter und Änlichem in der 
	
	
	
	





```
step()
```
 Methode verzichtet, um die Geschwindigkeit zu steigern.

```
import java.util.Map;

import org.jblocks.editor.BlockModel;
import org.jblocks.scriptengine.Block;
import org.jblocks.scriptengine.ByobBlock;
import org.jblocks.scriptengine.IScriptThread;
import org.jblocks.scriptengine.NativeBlock;

/**
 *
 * @author ZeroLuck
 */
public class DefaultScriptThread implements IScriptThread {

    private volatile boolean stopRequest = false;
    private StackElement stack;
    private final Map<String, Object> globalVariables;
    private final StackElement rootStack;

    public DefaultScriptThread(Map<String, Object> globalVariables, Block[] commands) {
        this.globalVariables = globalVariables;
        this.stack = new StackElement(null, new ByobBlock(commands.length, BlockModel.NOT_AN_ID, commands), commands, true, globalVariables);
        this.rootStack = stack;
    }

    public StackElement getStack() {
        return stack;
    }

    /**
     * @return true when the thread is finished otherwise false
     */
    public boolean step() {
        if (stopRequest || stack == null) {
            return true;
        }
        if (stack.off >= stack.commands.length) {
            if (stack.perform instanceof ByobBlock) {   // BYOB
                ByobBlock byob = (ByobBlock) stack.perform;
                Block[] seq = byob.getSequence();
                if (seq == stack.commands) {    // finish
                    stack = stack.parent;
                } else {
                    StackElement child = new StackElement(stack.parent, byob, byob.getSequence(), false, globalVariables);
                    System.arraycopy(stack.param, 0, child.param, 0, stack.param.length);
                    stack = child;
                }
            } else if (stack.perform instanceof NativeBlock) {    // NATIVE
                NativeBlock nat = (NativeBlock) stack.perform;
                StackElement parentBackup = stack.parent;
                Object val = nat.evaluate(stack, stack.param);

                stack = stack.parent;

                if (stack == parentBackup) {
                    if (stack.doParam) {
                        stack.param[stack.off - 1] = val;
                    }
                }
            } else {
                throw new java.lang.UnsupportedOperationException("block '" + stack.perform + "' isn't supported");
            }
        } else {
            Object val = stack.commands[stack.off];
            stack.off++;
            if (val instanceof Block) {
                Block cmd = (Block) val;
                StackElement child = new StackElement(stack, cmd, cmd.getParameters(), true, globalVariables);
                stack = child;
            } else {
                if (stack.doParam) {
                    stack.param[stack.off - 1] = val;
                }
            }
        }
        return false;
    }

    @Override
    public void stop() {
        stopRequest = true;
    }

    @Override
    public boolean isAlive() {
        return !stopRequest;
    }

    @Override
    public Object getReturnValue() {
        if (rootStack.param.length == 0) {
            return null;
        }
        return rootStack.param[0];
    }
    
    public static class StackElement {

        int off = 0;
        StackElement parent;
        
        final Block perform;
        final Object[] commands;
        final Object[] param;
        final Map<String, Object> global;
        final boolean doParam;

        public StackElement(StackElement parent, Block perform, Object[] commands, boolean doParam, Map<String, Object> global) {
            this.perform = perform;
            this.parent = parent;
            this.commands = commands;
            this.param = new Object[perform.getParameterCount()];
            this.doParam = doParam;
            this.global = global;
        }
    }
}
```


----------



## Cola_Colin (30. Mai 2012)

Es ist eben ein Problem des Skript-Nutzers, dass er in einem doTick NICHT mal eben PI auf 1 Billionen Stellen berechnen sollte.
Wenn er das will kann er ja aus doTick heraus selber einen Thread starten. Mir fallen aber nicht viele Dinge ein, die man für ein Spiel machen wollen würde, die einen Extrathread benötigen. Ein wirklich großes Problem ist es also nicht. Hab selber mal ein ähnliches Konzept umgesetzt und wirklich Zeitaufwendiges hab ich in doTick eigentlich nie machen müssen. Weder für ein Tetris, noch für ein Packman, noch für ein Super Mario...


----------



## HalloWelt_ (30. Mai 2012)

Es ist in meinem Fall einfach nicht möglich mit einem Tick-System zu arbeiten. Ich weiß, dass ein Tick System für Spiele deutlich besser wäre, aber in dem Programm soll sich der Benutzer nicht um solche Sachen kümmern müssen. ( Das Programm ist vorwiegend für Kinder und man soll damit aus Blöcken/Scripts und Grafischen Objekten seine Spiele und Simulationen selber programmieren können. Wie  Scratch | Home | imagine, program, share). Also weg von dem Tick-System und zurück zu dem parallelem Ausführen von den Scripten...


----------



## Reggie (30. Mai 2012)

Wieso verwendest du nicht einen Threadpool? 
Das Problem bei vielen Thread ist die Erstellung und die Vernichtung derselbigen, mit einem Threadpool unterbindest du meines Wissens beides.

Ich kenn mich nicht besonders damit aus aber ich denke das müsste gehen.

Gruss Reggie


----------



## SlaterB (30. Mai 2012)

ein ThreadPool, jedenfalls die üblichen Varianten, zaubert auch nicht, wenn er 8 Threads hat und 100 Runnable übergeben bekommt,
dann arbeitet er erst die ersten 8 Runnable komplett ab, dann die nächsten 8, bzw. wann immer was frei wird einen neuen usw.,
nicht alle 100 gleichzeitig mit Unterbrechung, dafür braucht man schon 100 Threads, ob direkt in Java oder selber nachgebaut,

wenn schon nachbauen, würde ich aber wirklich die Skripte miteinbeziehen, 
mit normalen Java-Mitteln Untermethoden + Schleifen, feste Unterbrechungspunkte, freiwillige Rückfragen beim Framework usw., 
die schon genannten Ticks,

wie sonst vorzugehen ist, Skripte wirklich anhalten und der Speicher sichern, kann ich mir nur schwer vorstellen,
der Code oben sieht nicht unbedingt nach großer Magie aus, 
sind NativeBlock usw. normale Java-Klassen oder wird da irgendwie größer angegangen, wie auch immer das möglich sein sollte?


----------



## mggrossi (30. Mai 2012)

Also ich hab leider zwar nix zur Lösung, aber dein Projekt klingt echt interessant.
was willst später damit machen, wenns fertig ist. Gibts das dann irgendwo als download?


----------



## HalloWelt_ (30. Mai 2012)

Thread-Pools sind wie SlaterB schon sagte keine Lösung für das Problem.



> der Code oben sieht nicht unbedingt nach großer Magie aus,
> sind NativeBlock usw. normale Java-Klassen oder wird da irgendwie größer angegangen, wie auch immer das möglich sein sollte?



Ein NativeBlock ist in der Anwendung ein Block (siehe Bild unten) welcher in Java implementiert ist, und nicht wie ByobBlöcke intern
aus einer Sequenz von anderen Blöcken besteht. Ein Block ist einfach ein Befehl der auch etwas zurück liefern kann und zum Beispiel als Parameter übergeben werden kann. Über den Namen "NativeBlock" könnte man sich streiten...

Ein NativeBlock wird bei meiner aktuellen Multithreading Implementierung immer atomar ausgeführt.
(Anders würde es mit einem Thread gar nicht gehen). Das Problem ist jetzt, dass Schleifen u.ä. auch als NativeBlock implementiert sind, und daher die Schleife nicht einfach komplett bearbeitet werden kann. (Sonst würden andere Scripte solange still stehen). Der NativeBlock muss daher selbstständig aufhören und den Stack so manipulieren, dass er beim nächsten 
	
	
	
	





```
step()
```
 wieder an die Reihe kommt und mit der Ausführung weiter machen kann.
Der 
	
	
	
	





```
while() { }
```
 Block sieht zum Beispiel so aus:


```
WHILE = new NativeBlock(4, WHILE_ID) {

            @Override
            public Object evaluate(Object ctx, Object... params) {
                if (params[0] == null) {
                    return null;
                }

                Block exp = ((Block[]) params[0])[0];

                if (params[2] == null) {
                    StackElement me = new StackElement(((StackElement) ctx).parent, this, empty, true, ((StackElement) ctx).global);
                    me.off = 3;
                    me.param[0] = params[0];
                    me.param[1] = params[1];
                    me.param[2] = false;
                    me.param[3] = null;
                    ((StackElement) ctx).parent = new StackElement(me, exp, exp.getParameters(), true, ((StackElement) ctx).global);

                } else if (params[2] == Boolean.TRUE) {
                    Block[] seq = (Block[]) params[1];
                    Object offObj = params[3];
                    int off = 0;
                    if (offObj != null) {
                        off = (Integer) offObj;
                        if (off >= seq.length) {
                            StackElement me = new StackElement(((StackElement) ctx).parent, this, empty, false, ((StackElement) ctx).global);
                            me.param[0] = params[0];
                            me.param[1] = seq;
                            me.param[2] = null;
                            me.param[3] = null;
                            ((StackElement) ctx).parent = me;
                            return null;
                        }
                    }
                    StackElement me = new StackElement(((StackElement) ctx).parent, this, empty, false, ((StackElement) ctx).global);
                    me.param[0] = params[0];
                    me.param[1] = seq;
                    me.param[2] = true;
                    me.param[3] = off + 1;

                    if (off >= seq.length) {
                        ((StackElement) ctx).parent = me;
                        return null;
                    }
                    Block b = seq[off];
                    StackElement cmd = new StackElement(me, b, b.getParameters(), true, ((StackElement) ctx).global);

                    ((StackElement) ctx).parent = cmd;
                }
                return null;
            }
        };
```

Meiner Meinung nach ist das schon recht kompliziert zu durchschauen...
Deswegen suche ich nach einer anderen Lösung, da es auf Dauer nicht so schön klappt.
Ich habe zwar schon über 15 Blöcke so implementiert, aber sobald ich einen Fehler finde bin ich wieder 2-5. Tage beschäftigt den nächsten zu finden. :autsch:



> Also ich hab leider zwar nix zur Lösung, aber dein Projekt klingt echt interessant.
> was willst später damit machen, wenns fertig ist. Gibts das dann irgendwo als download?



Das Programm soll es Anfängern und Kindern ermöglichen mit Blöcken ein Programm zusammen zu stecken. (siehe Bild)
Ich möchte noch keinen download zur Verfügung stellen, da das Programm noch nicht stabil ist.
Ein Screenshot kann ich aber zeigen:


----------



## mggrossi (30. Mai 2012)

Also die Idee ist einfach klasse,
zumal es für so was immer kundschaft gibt.
Siehe 3D GameStudio oder Klick und Play.... die ich beide habe...
Und der Screenshot sieht klasse aus.

Also würd mich freuen wenn du mich / uns hier auf dem laufenden hällst.

Viele grüße
Grossi


----------



## schalentier (30. Mai 2012)

Sieht nach nem interessanten Projekt aus, aber wozu brauchst du da Threads? Reicht es denn nicht, einfach alle Bloecke der Reihe nach abzuarbeiten und gudd?

Des weiteren wuerde ich das ganze Konzept in Frage stellen (sorry). Wo ist der Vorteil von diesen Bloecken im Vergleich zu normalem Quellcode? Die beiden Bloecke aus dem Screenshot saehen mit Ruby z.B. so aus:


```
def is_prim number
  if number < 2
    return false
  end
  cnt = 2
  while cnt < number
    if number%cnt==0
       return false
    end
    cnt += 1
  end
  return true
end

number = 2
while true
  if is_prim(number)
    puts "#{number} ist eine Primzahl"
  end
  number += 1
end
```


----------



## HalloWelt_ (30. Mai 2012)

Ich brauche Threads, da natürlich auch mehrere Scripts auf einmal laufen können!
Das Bild mit dem Primzahlen-Programm könnte man natürlich auch ganz einfach in einer "echten" Programmiersprache formulieren.
Es geht aber eher darum, später Grafischen-Objekte auf einfache Weiße in Aktion zu setzen. Das Programm ist allgemein eher für Kinder/Programmieranfänger und nicht für professionelle Programmierer gedacht. Du wirst nicht glauben wie kompliziert eine Programmiersprache für Programmieranfänger ist wenn sie noch ganz am Anfang stehen: Man kann Fehler machen mit der Syntax und ist dann verzweifelt weil man es nicht versteht. (Kann man sich eigentlich gar nicht vorstellen ist aber so  ) Mit den Blöcken kann einem das nicht passieren! An Scratch kann man sehr gut sehen, dass dies sehr beliebt ist.


----------



## gassssst (31. Mai 2012)

Hört sich interessant an, inwiefern ist dein Programm anders als Scratch?
(Und warum hast du nicht Scratch erweitert anstatt das meiste selbst neu zu implementieren?)


----------



## HalloWelt_ (31. Mai 2012)

gassssst hat gesagt.:


> Hört sich interessant an, inwiefern ist dein Programm anders als Scratch?
> (Und warum hast du nicht Scratch erweitert anstatt das meiste selbst neu zu implementieren?)



- Das Programm erlaubt das programmieren eigener Blöcke in Java (in Scratch ist dies kompliziert und nur Squeak möglich) und zwar auf einfache weiße.
- Es gibt von Grund aus schon Datei-/Netzwerkblöcke.
- Die "Bühne" hat kein Limit mehr. Man kann sie vergrößern und muss nicht immer 480x360 verwenden-.
- Außerdem ist die Anwendung sehr viel schneller bei der Ausführung von Scripts. 

Ich wollte Scratch nicht erweitern, da ich kein Squeak/Smalltalk kann und die Zukunft von Scratch leider in Flash liegt. 
Der SourceCode der neuen Version ist auch noch nicht verfügbar. (Bei dem Programmiertempo von denen, wird das wohl auch noch ein bisschen dauern )


----------

