Synchronisation von Listen bei Zugriffen durch mehrere Prozesse

Tharsonius

Bekanntes Mitglied
Hallo zusammen,

ich arbeite aktuell an einem Programm, welches die serielle Schnittstelle nutzt. Soweit so gut, RXTX funktioniert auch.
Nun kommt aber dazu, dass ich mehrere Threads habe.

Prozess 1 schreibt auf die serielle Schnittstelle,
Prozess 2 liest azf der seriellen Schnittstelle und
Prozess 3 ist das Programm selber, welches auch die Daten verarbeitet.

Dazu habe ich 2 Listen generiert, die als Puffer fungieren. Beide befinden sich in Prozess 3. Dazu gibt es entsprechende Methoden, um auf besagte Listen zugreifen. Das ganze sieht dann so aus:


Prozess 1 - gekürzt
Java:
final class SerialWriter
extends Thread
{
  private OutputStream stream;
  private Serial serial;
  private boolean run;
  
  protected SerialWriter(Serial serial, OutputStream stream)
  {
    this.stream = stream;
    this.serial = serial;
    run = true;
  }
  
  protected final void exit()  // Hierüber wird von Prozess 3 aus dieser Prozess beendet
  {
    run = false;
  }
  
  public final void run ()
  {
    Nachricht message;
    while(run == true)
    {
      message = serial.getSendMessage();  // Hier wird die Nachricht aus Prozess 3 geholt.
      if(message == null)
      {
        try
        {
          sleep(200);
        }
        catch(InterruptedException e)
        {
          //Nix tun
        }
      }
      else
      {
        try
        {
          stream.write(message.getID());
          
          [...]
          
        }
        catch(IOException e)
        {
          serial.sendefehler();   // Hier wird ein eventueller Fehler an Prozess weiter gereicht
        }
      }
    }
  }
}


Prozess 2 - gekürzt
Java:
final class SerialReader
extends Thread
{
  private InputStream stream;
  private Serial serial;
  private boolean run;
  
  protected SerialReader(Serial serial, InputStream stream)
  {
    this.stream = stream;
    this.serial = serial;
    run = true;
  }
  
  protected final void exit()  // Hierüber wird von Prozess 3 aus dieser Prozess beendet
  {
    run = false;
  }
  
  public final void run ()
  {
    int wert;
    boolean gewartet;
    while(run == true)
    {
      gewartet = false;
      try
      {
        wert = stream.read();
      }
      catch(IOException e)
      {
        serial.empfangfehler();
        wert = -1;
      }
      if(wert == -1)
      {
        try
        {
          sleep(100);
        }
        catch(InterruptedException e)
        {
          //Nix tun
        }
      }
      else
      {
        EmpfangsNachricht message = new EmpfangsNachricht();
        try
        {
          message.setzeID(wert);
          wert = stream.read();
          if(wert == -1 && gewartet == false)
          {
            try
            {
              sleep(100);
            }
            catch(InterruptedException e)
            {
              //Nix tun
            }
            gewartet = true;
            wert = stream.read();
          }
          if(wert == -1)
          {
            message.setzeStatus(false);
            serial.setReceiveMessage(message);
            continue;
          }
          message.setzeDLC(wert);
          
          [...]
          
          
          serial.setReceiveMessage(message);  // Hier wird die nachricht an Prozess 3 weiter gereicht
        }
        catch(IOException e)
        {
          serial.empfangfehler();     // Hier wird ein eventueller Fehler an Prozess weiter gereicht
        }
      }
    }
  }
}


Prozess 3 - relevante Teile
Java:
public final class Serial
{
  private LinkedList<Nachricht> sendepuffer;
  private LinkedList<EmpfangsNachricht> empfangspuffer;
  private SerialReader reader;
  private SerialWriter writer;

  public Serial()
  {
    sendepuffer = new LinkedList<Nachricht>();
    empfangspuffer = new LinkedList<EmpfangsNachricht>();
  }

  public final boolean open(String port, int baud)
  {
    [ ... ]

    InputStream in;
    OutputStream out;
    try
    {
      in = serialPort.getInputStream();
      out = serialPort.getOutputStream();
    }
    catch(IOException e)
    {
      serialPort.close();
      return false;
    }
    reader = new SerialReader(this, in);
    reader.start();
    writer = new SerialWriter (this, out);
    writer.start();
    geoeffnet = true;
    return true;
  }

  public synchronized final boolean close()
  {
    if(geoeffnet == false)
      return true;
    reader.exit();
    writer.exit();
    serialPort.close();
    geoeffnet = false;
    return true;
  }

  public synchronized final boolean transmit(Nachricht message)
  {
    sendepuffer.add(message);
    return true;
  }
  
  public synchronized final EmpfangsNachricht receive()
  {
    EmpfangsNachricht message = empfangspuffer.remove();
    return message;
  }
  
  protected synchronized final Nachricht getSendMessage()
  {
    Nachricht message = sendepuffer.remove();
    return message;
  }
  
  protected synchronized final void setReceiveMessage(EmpfangsNachricht message)
  {
    empfangspuffer.add(message);
  }
  
  protected synchronized final void empfangfehler()
  {
    fehler = true;
  }
  
  protected synchronized final void sendefehler()
  {
    fehler = true;
  }
}


Meine Frage ist nun, habe ich dies mit dem synchronized bei den Methoden richtig gemacht?
Muss ich die Methoden synchronisieren oder muss ich das ganze eventuell ganz anders machen.
Ich muss zugeben, so 100% blicke ich das noch nicht.

Was ich halt verhindern will ist, dass eine Nachricht eventuell nur halb in einen ansonsten leeren Puffer geschoben wird und dann eine Auswertung erfolg und alles irgendwie vor die Wand läuft.

Also habe ich mir gedacht, in Prozess 3 muss ich die Zugriffsmethoden auf die Liste, egal von welcher Seite synchronisieren, oder?


Außerdem möchte ich natürlich die Prozesse 1 und 2 sauber beenden, wenn die Serielle Schnittstelle nicht mehr benötigt wird. Ob nach beenden Aufforderung bis zum wirklichen beenden noch etwas in den Puffer geschrieben wird ist dabei egal, da dies nicht mehr verarbeitet wird und etwas später sowieso verworfen wird.

Ich hoffe Ihr könnt mir sagen, ob das so passt oder eher vollkommener Blödsinn ist.
 
Bin jetzt nicht so wirklich aus dem Code schlau geworden, was Du vorhast.

Auf die Listen sende- und empfangspuffer ist der Zugriff nur synchronized möglich, das war ja das was Du wolltest !? Könntest Dir auch mal ConcurrentLinkedQueue anschauen.

Allerdings wird (soweit ich das richtig überblickt habe) nur von jeweils einem Thread auf eine Liste zugeriffen, daher ist das synchronized eigentlich nicht notwendig. Und es stellt sich die Frage was erhoffst Du damit zu erreichen?

Genauso bei
Code:
synchronized final void empfangfehler()
und
Code:
synchronized final void sendefehler()
beide mal setzt Du fehler auf true und ich sehe auch nirgends, dass fehler zurückgesetzt wird. Das ist jetzt nicht unbedingt verkehrt, aber auch nicht notwendig, daher auch hier die Frage warum?

Ansonsten: Für
Code:
while(run == true)
kann man auch einfach
Code:
while(run)
und für
Code:
if(geoeffnet == false)
einfach
Code:
if(!geoeffnet)
schreiben- ist aber auch ein bisschen Geschmacksache.

Dann noch dieses sleep(...) an mehreren Stellen. Kann es sein, dass Du bei leerer Liste damit warten willst um dann erneut zu überprüfen, ob was in der Liste drin steht? Wenn ja, dann informiere den Thread einfach wenn es in der Liste was zu lesen gibt bzw. wenn sich der Inhalt der Liste verändert.
 
Na ja, zu dem was ich machen will, ich will Daten über die serielle Schnittstelle versenden und empfangen. An der Gegenseite wird sich ein Microcontroller mit entsprechender Schaltung befinden, der sporadisch Daten sendet, die ich dann empfangen und verarbeiten muss. Auch muss ich selber einige Einstellungsdaten an den Controller senden. Aber das ist grundlegend erst mal egal, mir geht es nur darum ob ich das mit der synchronisation so richtig verstanden habe.


Der SerialWriter greift auf den sendepuffer zu, der SerialReader auf den Empfangspuffer. Beide laufen als separate Threads.
Die Klasse Serial wiederum ist in einem ganz anderen Thread untergebracht, meinem eigentlichen Programm.

Dieses Programm wiederum hat eigene Methoden receive und transmit, um jeweils auf die Puffer zuzugreifen.

Mir geht es derzeit nur darum, ob ich das mit der synchronisation so richtig habe, dass man die Methoden synchronisiert oder ob man das halt doch etwas anders machen muss.



Was den Fehler angeht, der wird durchaus noch ausgelesen und kann auch zurück gesetzt werden, diese beiden Methoden habe ich aber noch nicht. Das zurücksetzen würde ich auch als synchronisierte Methode schreiben. Dieser Bereich ist noch nicht 100%ig fertig ;-)



Der Gedanke zu dem Sleep ist, dass ich, solange nix zu senden ist einfach warte und wenn gerade nix empfangen wurde, dann ebenfalls warte. Aber bei leerer Liste zu informieren ist eine Idee, zumindest was das senden angeht. Empfangsseitig wird das nicht gehen. Muss mir das mal raussuchen, wie ich das entsprechend ändern kann.
 
Du hast ein typisches Producer/Consumer Problem. Google mal danach und mache es wie vorgeschlagen. sleep und aktives Polling ist nicht der Bringer.
Dringender Vorschlag: das java.util.concurrency Package und eine Queue.
 
So gesehen habe ich das ganze also 2 mal:
Einmal ist mein Hauptprogramm der Producer und der SerialWriter der Consumer, und zum anderen ist mein SerialReader ein anderer Producer wärend mein Hauptprogramm hier der Consumer ist.

Nur mit der Abwandlung, dass das Objekt, was die Listen verwaltet bei mir ebenfalls im Hauptprogramm läuft.
In den Beispielen sind Producer und Consumer immer separate Threads und das Listenobjekt liegt im Hauptprogramm. Das ist bei mir anders, hier muss ich mal schauen wie das umsetzbar ist.


Bezüglich Queue, da habe ich Listen genommen, gehen die nicht?
Alles in allem werden die Anzahl der Daten überschaubar sein, es darf keine Nachricht verloren gehen, die Queue darf sozusagen niemals voll werden. Zu sendende Nachrichten werden nur wenige produziert und empfangene Nachrichten werden praktisch sofort verarbeitet.
 
Zuletzt bearbeitet:
Mir geht es derzeit nur darum, ob ich das mit der synchronisation so richtig habe, dass man die Methoden synchronisiert oder ob man das halt doch etwas anders machen muss.
Da bin ich mir nicht so ganz sicher. Mit den synchronized Methoden erreicht man, dass solange die Methode abgearbeitet wird nur ein Thread diese und weitere synchronzied Methoden auf diesem Objekt aufrufen kann, alle anderen Threads müssen warten.
D.h. da in der Klasse Serial so ziemlich alle Methoden synchronized sind, kann solange z.B. der SerialReader die
Code:
setReceiveMessage(message);
abarbeitet, der SerialWriter kein getSendMessage() oder sendefehler() aufrufen - und das ist glaube ich nicht deine Absicht. Der Reader soll doch sicherlich den Empfangspuffer bearbeiten können, auch wenn der Writer gerade was aus dem Sendepuffer ausliest.
 
Das heißt ich müsste die Puffer entsprechend auslagern und daraus separate Objekte machen. Serial wird dann auf die Puffer zugreifen, je nachdem was es ist wird dann der entsprechende Puffer angesteuert und kann ich beides parallel machen?

Ich glaube so laaaangsam, aber wirklich nur langsam lichtet sich etwas der Nebel ;-)
Ich muss da glaub ich erst mal noch etwas mehr lesen...
 
Moin,

Du brauchst nur 2 Threads ... Lesen und Schreiben sind untereinander atomar ... so wie Du Dein Byte-Array an das BS übergeben hast liegt es am BS ... selbst die Hardware kümmert sich schon darum das auf der Leitung nichts kollidiert

der eine Thread kümmert sich nur um das einlesen ... dabei ist ein lokes Byte-Array von Vorteil ... immerhin hast Du einen Sequence-Anfang (0xff) - persönlich halte ich aber Dein Sequence-Ende (0xff) für ungünstig ... ich bevorzuge hier einen Sequence-Anfang und im nächsten Byte eine entsprechende Längenangabe ... wenn Dein Paket immer X Bytes ist, kannst Du Dir die Längenangabe sparen

Code:
public byte [] serialRead()
{
  int position = 0;
  byte [] message = new byte [5]; // bei fester länge, sonst init nachdem Länge steht
  while(true)
  {
    int b = 0xff & serial.read();
    if (b == 0xff) position = 0; // Reset
    message[position++] = b; // Index testen !!
    if (position > message.length) break; // Paket erhalten
  }
  return message;
}

allerdings musst Du selber entscheiden wie lange die Verarbeitung des Paket dauert ... bei mir hat es bisher immer ausgereicht, die Daten im selben Thread zu verarbeiten ... ansonsten in eine Queue packen ... dafür entsprechend beiden Methoden mit synchronized absichern ... und über einen bzw. andere Threads abarbeiten -> Consumer/Producer-Problem (wurde aber schon genannt)

hand, mogel
 
@Michael... : Werde ich mir mal anschauen.

@mogel: Wieso Sequenz Anfang / Ende? Wieso 0xff? Davon habe ich nie was geschrieben. Meine Nachricht besteht aus ID, Datenlängenfeld, 0-8 Daten und CRC, dabei kann jeder Wert 0x00-0xff sein.
Aber davon hatte ich bewusst nix geschrieben.
 
dann misintepretierte ich Dinge wie

Java:
if(wert == -1)

zumindest hast Du ein Protokoll - was die meisten vergessen vorher zudefinieren
 
Ich hab das ganze jetzt gelöst, danke an alle für die Tips.

Ich habe zwei Puffer Objekte erstellt, einmal für Empfang, einmal für senden.

In meinem Hauptprogramm erstelle ich nun die beiden Puffer und übergebe diese an meine SerialReader und Serial Writer, sie als separate Threads laufen.

Zugriff auf den Puffer ist jeweils über synchronized(puffer) {}

Auch habe ich das polling durch ein wait() / notify() abgesichert, soweit möglich.
 
Ich hab das ganze jetzt gelöst, danke an alle für die Tips.

Ich habe zwei Puffer Objekte erstellt, einmal für Empfang, einmal für senden.

In meinem Hauptprogramm erstelle ich nun die beiden Puffer und übergebe diese an meine SerialReader und Serial Writer, sie als separate Threads laufen.

Zugriff auf den Puffer ist jeweils über synchronized(puffer) {}

Auch habe ich das polling durch ein wait() / notify() abgesichert, soweit möglich.

Sieht nach einer sinnvollen Lösung aus. Sieht auch so aus, als hättest du verstandes was du da machst.. die Frage davor sah noch nicht so aus ;-)
 
Sieht nach einer sinnvollen Lösung aus. Sieht auch so aus, als hättest du verstandes was du da machst.. die Frage davor sah noch nicht so aus ;-)

na ja, obs Sinnvoll ist werde ich sehen. Ja, das erste war... ein erster Versuch...
Ich hatte keinen Peil und hab mir das so in etwa gedacht und deshalb mal nachgefragt. Na ja, hab das erste mal mit mehreren Prozessen zu tun.. Aber hey, man lernt ja ;-)

Nochmals Danke für den Anstoß in die richtige Richtung.
 

Zurück
Oben