Seite 1 von 1
Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Samstag 19. Januar 2019, 22:07
von Honig
Guten Abend,
ich möchte auf einen GPIO Pin am Raspberry Pi, die Impulse innerhalb einer Sekunde zählen und das durchgehend.
Folgenden Code habe ich momentan:
Code: Alles auswählen
import RPi.GPIO as GPIO
import sys
import time
sys.setrecursionlimit(100000)
GPIO.setmode(GPIO.BCM)
GPIO.setup(21, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
count=0
def call_count(n):
global count
count+=1
try:
while 1:
GPIO.add_event_detect(21, GPIO.RISING)
GPIO.add_event_callback(21, call_count)
time.sleep(1)
GPIO.remove_event_detect(21)
print count
count=0
GPIO.add_event_detect(21, GPIO.RISING)
GPIO.add_event_callback(21, call_count)
except KeyboardInterrupt:
GPIO.cleanup()
Der Code funktioniert auch einwandfrei. Nur nach ein paar Sekunden und Ausgaben von so 1500, bekomme ich die Fehlermeldung Speicherzugriffsfehler.
Es klingt für mich so, als würde ich den Speicher zumüllen, aber ich wüsste nicht wie. Denn die Variabel count überschreibe ich ja jedes Mal.
Soweit ich mich schon informiert habe, soll der rekursive Programmierstil in Python nicht so gut sein, aber ich habe da keine Ahnung wie ich das sonst lösen kann.
Hat vielleicht jemand eine Idee was der Fehler ist?
LG
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Samstag 19. Januar 2019, 22:21
von sparrow
Wo siehst du denn in dem Code Rekursion?
Und globale Variablen verwendet man nicht. Kann man der Funktion keine Parameter übergeben?
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Samstag 19. Januar 2019, 22:23
von __deets__
Was du da tust sollte zwar eigentlich klappen. Aber es ist extrem ungewöhnlich und darum auch nicht so Wahnsinnig überraschend, dass es kaputt geht. Lass das halt einfach. Ein Event meldet man einmal an.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Samstag 19. Januar 2019, 22:32
von __blackjack__
@Honig: Also erst einmal solltest Du aufhören am Rekursionslimit rumzuspielen. Warum machst Du das? Hier ist nichts rekursives im Programm!
Dann sollte auf Modulebene nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Keine globalen Variablen. Vergiss, dass es ``global`` gibt. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Verwende `True` und `False` wenn Du Wahrheitswerte meinst, nicht 1 und 0.
``as`` beim Import benutzt man um etwas importiertes umzubenennen. Hier wird aber nichts umbenannt, sondern Du willst das `GPIO`-Modul genau unter *dem* Namen importieren: ``from RPi import GPIO``.
Wenn Du den Pin änderst, musst Du durch das gesamte Programm gehen und bei jeder 21 schauen ob die für den Pin steht und sie entsprechend ändern. Das macht Arbeit und ist fehleranfällig. Dafür definiert man sich eine Konstante. Ordentlich benannt weiss der Leser dann auch überall jetzt noch die 21 steht, was der Wert bedeuten soll.
Der `cleanup()`-Aufruf gehört in einen ``finally``-Zweig, denn man will ja nicht nur hinter sich aufräumen wenn das mit Strg+C abgebrochen wird, sondern *immer* wenn der ``try``-Block verlassen wird, aus welchen Gründen auch immer.
Ich vermute mal ganz stark das Problem liegt an dem ständigen registrieren von Callbacks. Verschwinden die denn wenn man `remove_event_detect()` aufruft? Grundsätzlich würde ich glaube ich auch das einfach sein lassen, denn was bringt Dir das denn? Lass doch einfach weiterzählen, denn *sofort* nach dem entfernen der Erkennung setzt Du sie doch wieder.
Ich würde den Zähler einfach in einer Klasse kapseln und den fröhlich vor sich hin zählen lassen. Und dann einfach
Code: Alles auswählen
while True:
counter.count = 0
time.sleep(1)
print(counter.count)
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Samstag 19. Januar 2019, 22:47
von __blackjack__
Ungetestet:
Code: Alles auswählen
#!/usr/bin/env python3
import time
from threading import RLock
from RPi import GPIO
PIN_NUMBER = 21
class Counter:
def __init__(self, pin_number):
self.lock = RLock()
self._count = 0
GPIO.setup(pin_number, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(pin_number, GPIO.RISING)
GPIO.add_event_callback(pin_number, self.increase_count)
@property
def count(self):
return self._count
@count.setter
def count(self, value):
with self.lock:
self._count = value
def increase_count(self, _channel=None):
with self.lock:
self.count += 1
def main():
GPIO.setmode(GPIO.BCM)
try:
counter = Counter(PIN_NUMBER)
while True:
counter.count = 0
time.sleep(1)
print(counter.count)
except KeyboardInterrupt:
pass
finally:
GPIO.cleanup()
if __name__ == '__main__':
main()
Edit: Lock korrigiert.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Samstag 19. Januar 2019, 23:03
von Sirius3
@__blackjack__: bei `increase_count` hast Du ein doppeltes Lock.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Samstag 19. Januar 2019, 23:11
von __blackjack__
@Sirius3: Ups, hab ein „reentrant lock“ draus gemacht. Threadprogrammierung ist doof.

Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 13:13
von Honig
Vielen Dank für eure raschen Antworten.
Ihr habt Recht, da ist nichts von Rekursion drinnen, ich hatte da etwas Falsches im Kopf abgespeichert.
@__blackjack__ auf die Idee mit einen weiteren Thread bin ich noch gar nicht gekommen. Und der Code schaut so auch wesentlich schöner aus, danke. Nur erfasst dieser Code nur ca. 1/3 der Impulse. Könne es sein, dass das an RLock liegt, wobei das für mich nicht viel Sinn ergeben würde?
Edit: Die Frequenz liegt ca bei 1,5kHz.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 14:33
von __deets__
Was passiert denn, wenn du das RLock entfernst, inklusive der with-Aufrufe? Das resultierende Programm ist nicht mehr korrekt, weil du dadurch das zuruecksetzen des Counters auf 0 zu einer "race-condition" machst, bei der du einzelne Schritte verlieren kannst. Aber wenn das groessenordnungs-maessig dann hinkommt, dann liegt es tatsaechilch an dem Lock.
Wenn nicht, dann wuerde ich den Umstieg auf eine andere GPIO-Library empfehlen. pigpio. Wuerde ich eh, aber erstmal die Ursache ermitteln.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 15:34
von __blackjack__
@Honig: Welcher weitere Thread‽
@__deets__: Die „race condition“ wird zu mehr Schritten führen wenn das Rücksetzen auf 0 deswegen nicht klappt, nicht zu weniger. Denke ich mal.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 15:48
von Sirius3
Also muß man das ganze Lockfrei programmieren:
Code: Alles auswählen
#!/usr/bin/env python3
import time
from RPi import GPIO
PIN_NUMBER = 21
class Counter:
def __init__(self, pin_number):
self._reset_count = 0
self._count = 0
GPIO.setup(pin_number, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(pin_number, GPIO.RISING)
GPIO.add_event_callback(pin_number, self.increase_count)
@property
def count(self):
return self._count - self._reset_count
def reset(self):
self._reset_count = self._count
def increase_count(self, _channel=None):
self._count += 1
def main():
GPIO.setmode(GPIO.BCM)
try:
counter = Counter(PIN_NUMBER)
while True:
counter.reset()
time.sleep(1)
print(counter.count)
except KeyboardInterrupt:
pass
finally:
GPIO.cleanup()
if __name__ == '__main__':
main()
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 15:55
von __blackjack__
Fühle ich mich ein wenig unwohl mit, allerdings kann das auch am alten Programmierer in mir liegen. Die Zahl wächst dann ja beständig. Aber in Python bricht die ja nicht nach 2 hoch irgendwas Bits um, und wächst wahrscheinlich auch wenn es lange läuft nicht so stark das es zu viel Speicher verbraucht.

Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 16:04
von __deets__
Alternativ koennte man in Counter einfach eine Flagge setzen, die dann die naechste Zaehloperation abfragt. Das geht natuerlich nur, wenn man garantieren kann, dass danach noch Zaehlereignisse kommen, und niemand nach dem Reset den Wert 0 / niedrige Werte erwartet.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 16:55
von Honig
Danke mal für eure Mühe.
__deets__ hat geschrieben: Sonntag 20. Januar 2019, 14:33
Was passiert denn, wenn du das RLock entfernst, inklusive der with-Aufrufe? Das resultierende Programm ist nicht mehr korrekt, weil du dadurch das zuruecksetzen des Counters auf 0 zu einer "race-condition" machst, bei der du einzelne Schritte verlieren kannst. Aber wenn das groessenordnungs-maessig dann hinkommt, dann liegt es tatsaechilch an dem Lock.
Größenordnungsmäßg stimmt es dann.
Wenn ich es jetzt richtig verstehe, meint ihr, dass beim count=0, Impulse dazugezählt werden, die eigentlich nicht in diese ein Sekundenzeitspanne hineingehören, oder ist es genau andersrum?
@Sirius3 ich werde diesen Code dann auch noch ausprobieren.
__deets__ hat geschrieben: Sonntag 20. Januar 2019, 16:04
Alternativ koennte man in Counter einfach eine Flagge setzen, die dann die naechste Zaehloperation abfragt. Das geht natuerlich nur, wenn man garantieren kann, dass danach noch Zaehlereignisse kommen, und niemand nach dem Reset den Wert 0 / niedrige Werte erwartet.
@__deets__ ich verstehe nicht ganz, was du meinst.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 17:14
von __deets__
Hast du grundsaetzlich verstanden, warum das Lock da ist? Ich vermute mal eher nicht, oder?
Nebenlaeufigkeit ist schwer. Warum? Weil man nicht garantieren kann, wann was passiert.
Eine Anweisung
counter =+ 1
sieht harmlos aus. Was aber hinter den Kulissen passiert ist
1) lade counter in ein register (das ist etwas im Prozessor, was zum Rechnen benutzt wird)
1) addiere 1 hinzu
1) speichere das Register wieder in counter
Klingt einfach. Nur wenn du jetzt gleichzeitig den Hauptthread den counter auf 0 setzen laesst, dann passiert da folgendes:
2) lade 0 in ein register
2) speicher das register in counter
Und jetzt nimmst du mal einfach alle Anweisungen her, und verschachtelst sie. Denn genau das kann beim nebenlaeufigken Programmieren passieren.
1) lade counter in ein register (das ist etwas im Prozessor, was zum Rechnen benutzt wird)
2) lade 0 in ein register
1) addiere 1 hinzu
1) speichere das Register wieder in counter
2) speicher das register in counter
So zB verlierst du eine Stelle. Denn das Ergebnis ist 0, obwohl eigentlich noch die Addition erfolgen muesste und es damit 1 waere.
Sirius3 umgeht das Problem, indem er einfach statt den counter so zu manipulieren einfach eine zweite Variable einfuehrt, deren Inhalt immer abgezogen wird. Da die Variable nur von einem Thread aus beschrieben wird, und nur im anderen gelesen (gleiches gilt fuer den counter selbst, nur mit umgedrehten Rollen der Threads) ist das sicher gegen Verlust von Zaehlereignissen.
Mein Vorschlag arbeitet leicht anders: da soll der Haupthread counter nicht manipulieren, aber setzt eine Flagge/Variable, die dann dem anderen Thread, der den Counter hochzaehlt, sagt: mach nicht nur dass, sondern resette den counter. Allerdings habe ich darueber nochmal nachgedacht, und bin mir im Moment nicht sicher, ob das wirklich hilft. Eher nicht. Denn hier kann es passieren, dass zwischen dem lesen des counters und dem danachfolgenden setzen der Flagge doch wieder ein Ereignis reinkommt - und damit zaehlt man nicht alles mit. Sirius3 Weg ist also besser.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 17:43
von __blackjack__
@__deets__: Das was Du da beschreibst kann nicht passieren weil ja beide Threads ihre ”eigenen Register” haben, also kann nicht die 1 auf die 0 addiert werden. Wenn das passieren könnte, dann ist der Code der den Kontextwechsel zwischen Threads macht ziemlich im Eimer.
@Honig: Was passieren kann wenn Du das `lock` entfernst ist das ein ``count=0`` den Counter *gar nicht* zurücksetzt. Das erhöhen von `count` um eins sind ja eigentlich zwei Schritt die nacheinander passieren: Alten Wert von `count` abfragen, und neuen um eins erhöhten Wert an `count` zuweisen. Und wenn das Rücksetzen dazwischen kommt, dann ist es als wäre es gar nicht passiert. Hier mal die beiden Threads in atomare Aktionen (bezüglich `count`) dargestellt mit thread.schritt) vor den einzelnen Schritten:
1.1) count = 0
2.1) tmp = count
2.2) count = tmp + 1
Wenn das so wie's da steht ausgeführt wird ist alles okay. `count` hätte danach den Wert 1. Auch das hier wäre okay:
2.1) tmp = count
2.2) count = tmp + 1
1.1) count = 0
`count` hätte danach den Wert 0. Problematisch ist diese dritte mögliche Variante:
2.1) tmp = count
1.1) count = 0
2.2) count = tmp + 1
Hiernach hat `count` den Wert 43 wenn es vorher den Wert `42` hatte. Die Zuweisung von 0 ist einfach verloren gegangen und es wurde weiter gezählt als wenn nichts gewesen wäre.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 17:47
von Honig
@__deets__ & @__blackjack__ danke für eure ausführliche Erklärung, jetzt habe ich verstanden was das Problem ist.
Ich hätte da dann noch eine andere Frage. Wenn ich jetzt den Raspberry Zero verwende, hat der Prozessor einen CPU-Takt von 1000 MHz und nur einen Core, dazu würde ich das Raspbian lite Betriebssystem verwende, welche Frequenz kann ich dann ca. höchstens am GPIO-Pin Messen? Es geht mir dabei jetzt um keine Zahl, sondern nur um die Größenordnung. Hat da jemand Ideen/Erfahrungen?
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 18:08
von __deets__
Das ist so nicht einfach zu beantworten. Das ist ein Linux-OS. Das macht noch ca 3 andere Sachen, waehrend dein Programm laeuft. Da ist die CPU-Frequenz an sich ueberhaupt nicht das Problem. Sondern das Tuning des Gesamtsystems.
Denn wenn du 20 Sekunden lang mit 20MHz oder so GPIO Daten gesampelt hast, dann kommt der Scheduler daher & schmeisst dein Programm erstmal von seiner Zeitscheibe - weil gerade ein Netzwerkpaket von Facebook oder was auch immer reingekommen ist. Und damit bricht ploetztlich die Datenrate auf wenige kHZ ein.
Was nun genau das richtige Vorgehen fuer dein Problem ist, kommt darauf an, was das System denn genau tun soll. Nur zaehlen? Wenn ja, dann nimm dir einen Arduino. Der kann das besser und zuverlaessiger. Kannst du ja an den PI haengen und per serieller Schnittstelle oder so abfragen, was gerade der Zaehlstand ist.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 18:11
von __deets__
@__blackjack__: yupp, da habe ich mich irgendwie verbaselt.
Re: Speicherzugriffsfehler: bei einer globalen Variabel, mit Wert 1500
Verfasst: Sonntag 20. Januar 2019, 19:37
von Honig
@__deets__ ok danke für deine Antwort, dann werde ich noch schauen wie ich es am besten mache.
Vielen Dank an euch Sirius3, __blackjack__ und __deets__ ihr habt mir sehr geholfen.
Schönen Abend noch
