Liste erweitern mit variabler Anzahl

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
mobby
User
Beiträge: 76
Registriert: Donnerstag 17. April 2014, 09:43

Sirius3 hat geschrieben:Auf jeden Fall mußt Du das Ermitteln der Signale vom Schreiben in die Datei oder Datenbank trennen, also in verschiedenen Threads und über Queues getrennt, nicht Listen, die sind dafür nicht geeignet.
Was bietet sich dann an? Eine Variable in der die absolute Anzahl übergeben wird? Und was spricht technisch gegen die Liste?

Und mit der Aussage das Skript stürtzt nicht ab ... dann kann ich mir ja den watchdog/deamontools sparen? Weil sonst hat das ja kaum Fehleranfälligkeit, außer dass jetzt z.B. die Datenbank Verbindung nicht funzt, aber die läuft auf dem gleichen System und es gibt auch keinen ersichtlichen Grund, dass die ausfallen sollte.
BlackJack

@mobby: Gegen eine Liste spricht, dass die nicht threadsafe ist, beziehungsweise man sehr genau aufpassen muss was man damit anstellt und das keine der Operationen Probleme bei nebenläufiger Benutzung machen. Während der Queue-Typ threadsafe ist, was auch entsprechend in der Dokumentation garantiert wird. Ansonsten kann man auch eine Zahl hernehmen und die am besten in einer Klasse kapseln und die kritischen Abschnitte in den Operationen mit Sperren (z.B. `threading.Lock`) absichern. Die Länge einer Liste zum Zählen von etwas zu verwenden, ist IMHO ein Hack/Missbrauch solange man den Inhalt nicht tatsächlich für irgendwas benötigt.

Was das abstürzen angeht: Die Datenbankverbindung kann Probleme machen, auch wenn beides auf dem selben Rechner läuft, zum Beispiel Zeitüberschreitungen wenn das System an sich überlastet ist, Abbrüche der DB-Verbindung weil der Massenspeicher voll ist und die DB deshalb keine Daten mehr entgegen nehmen kann, andere Probleme mit dem Medium oder dem Dateisystem, blockierte Tabellen weil ein anderer Prozess ein Lock auf die Tabelle oder Datensätze hält, (zu) voller Arbeitsspeicher, und alles woran man jetzt gerade nicht denkt, was aber mal passieren kann.
Benutzeravatar
mobby
User
Beiträge: 76
Registriert: Donnerstag 17. April 2014, 09:43

@BlackJack: Meinst du mit dem Queue-Typ folgendes?

Code: Alles auswählen

from collections import deque
queue = deque[]
queue.append("1")
BlackJack hat geschrieben:Ansonsten kann man auch eine Zahl hernehmen und die am besten in einer Klasse kapseln und die kritischen Abschnitte in den Operationen mit Sperren (z.B. `threading.Lock`) absichern. Die Länge einer Liste zum Zählen von etwas zu verwenden, ist IMHO ein Hack/Missbrauch solange man den Inhalt nicht tatsächlich für irgendwas benötigt.
Und wie mach ich das? :shock:
BlackJack

@mobby: Eine `collections.deque` kann man auch nehmen, aber üblicherweise verwendet man für die Kommunikation zwischen Threads eine `Queue.Queue`.

Wie man geteilte Daten absichert hängt davon ab wie darauf zugegriffen wird und was dabei jeweils schief laufen kann wogegen man sich deshalb absichern muss.
Benutzeravatar
mobby
User
Beiträge: 76
Registriert: Donnerstag 17. April 2014, 09:43

@BlackJack: Wie muss ich mir das vorstellen mit "queue.Queue"? Kann ich alle momentan mit Liste umgesetzen Dinge folgendermaßen ersetzen?

Code: Alles auswählen

import Queue
leading_edges = Queue.Queue()
Und dann anstatt "leading_edges.append(1)" folgendermaßen weiter?

Code: Alles auswählen

leading_edges.put(1)
Und als letztes anstatt "leading_edge_count = len(leading_edges)"

Code: Alles auswählen

leading_edge_count = leading_edges.qsize()
Nur wie leere ich die Reihe wieder? Bzw. muss ich zusätzlich mit "leading_edges.join" und "leading_edges.task_done()" arbeiten? Oder ist so schon alles erfüllt?

Ich weiß, ich stell mich da ein bisschen dappig an, aber ich bin nun mal kein Programmierer und dieser Teil ist momentan nur ein kleiner Bereich meines Projektes. Danke nochmal!
BlackJack

@mobby: `qsize()` ist nicht zuverlässig, das liefert die ungefähre Anzahl der Elemente in der Queue. Was auch in der Dokumentation stehen sollte. Du musst die auf der anderen Seite mit `get()` wieder heraus holen und *dort* zählen. Wenn Du `qsize()` die Anzahl feststellst und danach die Queue leerst kann nach dem ”zählen” und vor dem Leeren ja vom anderen Thread ein Element in die Queue gesteckt werden, welches dann nicht gezählt wurde und mit dem Leeren verloren geht. Das `get()` darf nicht blockierend sein, weil Du ja in regelmässigen Abständen den aktuellen Zählwert in die Datenbank schreiben möchtest.

Ob Du `task_done()` und `join()` brauchst oder überhaupt verwenden kannst, hängt davon ab wie das Programmende vorgesehen ist, und was dabei dann wichtig ist.

Ich würde hier aber wahrscheinlich nicht mit einer Queue arbeiten weil das alles etwas umständlich aussieht. Ein threadsicheres Zähler-Objekt mit einer `increase()`- und einer `get_and_reset()`-Methode erscheint mir irgendwie einfacher.

Code: Alles auswählen

from threading import Lock


class Counter(object):
    def __init__(self, value=0):
        self.value = value
        self.lock = Lock()

    def increase(self):
        with self.lock:
            self.value += 1

    def get_and_reset(self):
        with self.lock:
            result = self.value
            self.value = 0
        return result
Benutzeravatar
mobby
User
Beiträge: 76
Registriert: Donnerstag 17. April 2014, 09:43

@BlackJack: Besten Dank, habe ich das dann so richtig umgesetzt?

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-


import time
from contextlib import closing
from itertools import groupby
from threading import Thread
from threading import Lock
from time import sleep
import MySQLdb as db
import pifacedigitalio as piface


def insert_into_database(timestamp, date, time,  amount):
    with closing(
        db.connect(
		host='',
	 	user='', 
		passwd='', 
		db='')
    ) as connection:
        connection.cursor().execute(
            'INSERT INTO S0_Zaehler_01(Zeitstempel, Datum, Zeit, Impulsanzahl) VALUES (%s, %s, %s, %s)',
            (timestamp, date, time, amount)
        )
        connection.commit()


def blink_led():
    piface.digital_write(0,1)
    sleep(0.1)
    piface.digital_write(0,0)


class Counter(object):
    def __init__(self,value=0):
        self.value = value
        self.lock = Lock()

    def increase(self):
        with self.lock:
            self.value += 1

    def get_and_reset(self):
        with self.lock:
            result = self.value
            self.value = 0
        return result  


def write_leading_edge_count(count):
    intervall = 5
    timestamp = int(time.time() / intervall) * intervall
    while True:
	timestamp += intervall
        time.sleep(max(0, timestamp-time.time()))
        leading_edge_count = count.get_and_reset()
        datum = str(time.strftime("%d-%m-%Y"))
        uhrzeit = str(time.strftime("%H:%M:%S"))
        insert_into_database(timestamp, datum, uhrzeit, leading_edge_count)


def start_writer(count):
    thread = Thread(target=write_leading_edge_count, args=(count,))
    thread.setDaemon(True)
    thread.start()


def iter_values(frequency):
    while True:
        yield piface.digital_read(0)
        sleep(1.0 / frequency)


def iter_changes(frequency):
    for value, _ in groupby(iter_values(frequency)):
        yield value


def main():
    piface.init()
    count = Counter()
    start_writer(count)
    for value in iter_changes(100):
        if value == 1:
            count.increase()
            blink_led()

			
if __name__ == '__main__':
    main()
Funktioniert auf jeden Fall! Ich frage dann noch alle Fehler beim Verbinden zur MySQL ab und sende, falls ein Fehler auftritt, diesen über ein SMTP Modul an einen Emailempfänger. Code dazu folgt ;) Danke!
Benutzeravatar
mobby
User
Beiträge: 76
Registriert: Donnerstag 17. April 2014, 09:43

So, habe jetzt noch die erwähnten Funktionen eingebettet. Nur eine Frage ist noch offen, wenn ich das momentan auskommentierte sys.exit() versuche, schließt das ja nur diesen Thread. Wie kann ich an der Stelle das gesamte Skript stoppen? Danke!

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-


import sys
from contextlib import closing
from time import sleep
import MySQLdb as db
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText


def send_email(content, mysql_error_code):
    fromaddr = ""
    toaddr = ""
    msg = MIMEMultipart()
    msg['From'] = fromaddr
    msg['To'] = toaddr
    msg['Subject'] = "MySQL Error: " + mysql_error_code
    msg.attach(MIMEText(content))
    text = msg.as_string()
    server = smtplib.SMTP('', )
    server.set_debuglevel(1)
    server.ehlo()
    server.starttls()
    server.login('','')
    server.sendmail(fromaddr, toaddr, text)
    server.quit()


def insert_into_database(timestamp, date, time,  amount):
    try:
        with closing(
            db.connect(
		    host='',
	 	    user='', 
		    passwd='', 
		    db='')
        ) as connection:
            connection.cursor().execute(
                'INSERT INTO S0_Zaehler_01(Zeitstempel, Datum, Zeit, Impulsanzahl) VALUES (%s, %s, %s, %s)',
                (timestamp, date, time, amount)
            )
            connection.commit()
    except db.Error, e:
        mysql_error_code = str(e.args[0])
        mysql_error_message = str(e.args[1])
        content = "Es kann keine Verbindung zum Server hergestellt werden. \n\n" + mysql_error_message
	send_email(content, mysql_error_code)
#        sys.exit()	
Vielen Dank für Feedback!
Antworten