Threads werden erstellt und darauf gewartet, dass sie ausgeführt werden. C-Thread – was ist das? Einzel- und Mehrprozessorsysteme

Modul Einfädeln wurde erstmals in Python 1.5.2 als Fortsetzung des Low-Level-Threading-Moduls eingeführt. Threading-Modul vereinfacht die Arbeit mit Threads erheblich und ermöglicht die Programmierung des Starts mehrere Operationen gleichzeitig. Beachten Sie, dass Threads in Python am besten mit E/A-Vorgängen funktionieren, z. B. dem Herunterladen von Ressourcen aus dem Internet oder dem Lesen von Dateien und Ordnern auf Ihrem Computer.

Wenn Sie etwas tun müssen, das CPU-intensiv ist, sollten Sie sich das Modul ansehen Mehrfachverarbeitung, anstatt Einfädeln. Der Grund dafür ist, dass Python eine Global Interpreter Lock (GIL) enthält, die alle Threads innerhalb des Hauptthreads ausführt. Aus diesem Grund werden Sie feststellen, dass alles ziemlich langsam ist, wenn Sie einige threadintensive Vorgänge ausführen müssen. Daher konzentrieren wir uns darauf, was Threads am besten können: E/A-Operationen.

Eine kleine Einführung

Mit einem Thread können Sie einen langen Code ausführen, als wäre es ein separates Programm. Dies ähnelt dem Aufruf eines geerbten Prozesses, außer dass Sie eine Funktion oder Klasse anstelle eines separaten Programms aufrufen. Ich habe immer gefunden konkrete Beispiele extrem nützlich. Schauen wir uns etwas ganz Einfaches an:

Threading importieren def doubler(number): „““ Eine Funktion, die von einem Thread verwendet werden kann „““ print(threading.currentThread().getName() + „\n“) print(number * 2) print() if __name__ == "__main__": für i in range(5): my_thread = threading.Thread(target=doubler, args=(i,)) my_thread.start()

Hier importieren wir Threading-Modul und erstellen Sie eine reguläre Funktion namens Doubler. Unsere Funktion nimmt einen Wert und verdoppelt ihn. Außerdem wird der Name des Threads ausgegeben, der die Funktion aufruft, und am Ende wird eine Leerzeile gedruckt. Als nächstes erstellen wir im letzten Codeblock fünf Threads und führen jeden von ihnen nacheinander aus.

Mit Multithreading können Sie viele Routineprobleme lösen. Zum Beispiel das Hochladen von Videos oder anderem Material auf soziale Netzwerke, wie Youtube oder Facebook. Um Ihren Youtube-Kanal zu entwickeln, können Sie https://publbox.com/ru/youtube verwenden, das die Verwaltung Ihres Kanals übernimmt. YouTube ist eine großartige Einnahmequelle und je mehr Kanäle, desto besser. Ohne Publbox geht es nicht.

Beachten Sie, dass wir beim Definieren eines Threads sein Ziel auf „unser“ festlegen Verdoppelungsfunktion, und wir übergeben auch ein Argument an die Funktion. Der Grund dafür, dass der args-Parameter etwas seltsam aussieht, ist, dass wir eine Sequenz an die Doubler-Funktion übergeben müssen und diese nur ein Argument benötigt. Daher müssen wir am Ende ein Komma hinzufügen, um die Sequenz eines davon zu erstellen. Beachten Sie, dass Sie seine Methode aufrufen können, wenn Sie warten möchten, bis der Thread ermittelt ist verbinden(). Wenn Sie diesen Code ausführen, erhalten Sie die folgende Ausgabe:

Thread-1 0 Thread-2 2 Thread-3 4 Thread-4 6 Thread-5 8

Natürlich möchten Sie Ihre Ausgabe wahrscheinlich nicht auf stdout drucken. Dies könnte in großem Chaos enden. Stattdessen müssen Sie ein Python-Modul namens verwenden Protokollierung. Dies ist ein Thread-sicheres Modul und erledigt seine Aufgabe perfekt. Lassen Sie uns das obige Beispiel ein wenig aktualisieren und hinzufügen Protokollierungsmodul, und gleichzeitig benennen wir unsere Flows:

Protokollierung importieren, Threading importieren def get_logger(): logger = logging.getLogger("threading_example") logger.setLevel(logging.DEBUG) fh = logging.FileHandler("threading.log") fmt = "%(asctime)s - %( threadName)s - %(levelname)s - %(message)s" formatter = logging.Formatter(fmt) fh.setFormatter(formatter) logger.addHandler(fh) return logger def doubler(number, logger): """ A Funktion, die von einem Thread verwendet werden kann. __main__": logger = get_logger() thread_names = ["Mike", "George", "Wanda", "Dingbat", "Nina"] für i in range(5): my_thread = threading.Thread(target=doubler, name =thread_names[i], args=(i,logger)) my_thread.start()

Die größte Änderung an diesem Code ist die Hinzufügung von get_logger-Funktionen. Dieser Teil des Codes erstellt einen Logger, der zum Debuggen konfiguriert ist. Dadurch wird das Protokoll im aktuellen Arbeitsordner gespeichert (d. h. dort, wo das Skript ausgeführt wird) und dann legen wir das Format jeder Zeile für das Protokoll fest. Das Format umfasst einen Zeitstempel, einen Stream-Namen, eine Protokollierungsebene und eine protokollierte Nachricht. In der Doubler-Funktion ändern wir unsere Ausgabeanweisungen in Anweisungen Protokollierung.

Beachten Sie, dass wir den Logger an die Doubler-Funktion übergeben, wenn einen Thread erstellen. Denn wenn Sie in jedem Thread ein Protokollierungsobjekt definieren, erhalten Sie am Ende mehrere Singletons und Ihr Protokoll wird viele doppelte Zeilen enthalten. Schließlich benennen wir unsere Streams, indem wir eine Namensliste erstellen und dann jedem Stream mithilfe des Namensparameters einen bestimmten Namen zuweisen. Wenn Sie diesen Code ausführen, sollten Sie eine Protokolldatei mit folgendem Inhalt erhalten:

Diese Ausgabe ist ziemlich selbsterklärend, also machen wir weiter. Ich möchte in diesem Artikel noch ein weiteres Problem ansprechen. Wir werden über die Vererbung einer Klasse namens sprechen Einfädeln.Einfädeln. Schauen wir uns das vorherige Beispiel noch einmal an, aber anstatt den Thread direkt aufzurufen, erstellen wir unsere eigene Unterklasse. Hier ist der aktualisierte Code:

Protokollierung importieren Threading-Klasse importieren MyThread(threading.Thread): def __init__(self, number, logger): threading.Thread.__init__(self) self.number = number self.logger = logger def run(self): """ Ausführen der Thread „““ logger.debug(“Calling doubler“) doubler(self.number, self.logger) def get_logger(): logger = logging.getLogger(“threading_example“) logger.setLevel(logging.DEBUG) fh = logging .FileHandler("threading_class.log") fmt = "%(asctime)s - %(threadName)s - %(levelname)s - %(message)s" formatter = logging.Formatter(fmt) fh.setFormatter(formatter) logger.addHandler(fh) return logger def doubler(number, logger): „““ Eine Funktion, die von einem Thread verwendet werden kann „““ logger.debug(“doubler function executing“) result = number * 2 logger.debug( „Verdoppelungsfunktion endete mit: ()“.format(Ergebnis)) if __name__ == „__main__“: logger = get_logger() thread_names = [„Mike“, „George“, „Wanda“, „Dingbat“, „Nina“ ] für i in range(5): thread = MyThread(i, logger) thread.setName(thread_names[i]) thread.start()

In diesem Beispiel haben wir gerade die Klasse geerbt Einfädeln.Einfädeln. Wir haben die Zahl übergeben, die wir verdoppeln möchten, und wie zuvor auch das Protokollierungsobjekt übergeben. Aber dieses Mal werden wir den Stream-Namen anders konfigurieren, indem wir die Funktion aufrufen Name einsetzen im Thread-Objekt. Wir müssen immer noch start für jeden Thread aufrufen, aber denken Sie daran, dass wir dies nicht in der geerbten Klasse definieren müssen. Wenn Sie start aufrufen, wird Ihr Thread durch Aufrufen gestartet run-Methode. In unserer Klasse rufen wir die Doubler-Funktion auf, um unsere Berechnungen durchzuführen. Die Ausgabe ist der im Beispiel zuvor sehr ähnlich, außer dass ich der Ausgabe eine zusätzliche Zeile hinzugefügt habe. Probieren Sie es selbst aus und sehen Sie, was passiert.

Sperren und Synchronisierung

Wenn Ihnen mehr als ein Thread zur Verfügung steht, müssen Sie möglicherweise herausfinden, wie das geht Konflikte vermeiden. Damit meine ich, dass Sie den Fall verwenden können, dass mehr als ein Thread gleichzeitig auf dieselbe Ressource zugreifen muss. Wenn Sie nicht über solche Probleme nachdenken und entsprechend planen, kann es sein, dass die schlimmsten Probleme zu einem sehr ungünstigen Zeitpunkt auftreten, und zwar normalerweise dann, wenn der Code veröffentlicht wird.

Die Lösung des Problems ist Schlösser verwenden. Die Sperre wird vom Python-Modul bereitgestellt Einfädeln und kann einen Thread enthalten oder überhaupt keinen Thread. Wenn ein Thread versucht, eine Sperre für eine bereits gesperrte Ressource zu erlangen, wartet dieser Thread, bis die Sperre aufgehoben wird. Schauen wir uns ein praktisches Beispiel für einen Code an, der keine Sperrfunktion hat, aber wir versuchen, sie hinzuzufügen:

Import threading total = 0 def update_total(amount): „““ Aktualisiert die Gesamtsumme um die angegebene Menge „““ global total total += amount print (total) if __name__ == „__main__“: for i in range(10): my_thread = threading.Thread(target=update_total, args=(5,)) my_thread.start()

Wir können dieses Beispiel noch interessanter machen, indem wir einen Aufruf hinzufügen Zeit.Schlaf. Daher besteht hier das Problem darin, dass ein Thread aufrufen kann update_total und bevor es aktualisiert wird, kann es sein, dass ein anderer Thread es aufruft und versucht, es ebenfalls zu aktualisieren. Abhängig von der Reihenfolge der Operationen kann der Wert einmal addiert werden. Fügen wir der Funktion eine Sperre hinzu. Es gibt zwei Möglichkeiten, dies zu tun. Der erste ist die Verwendung versuchen/endlich, wenn wir sicherstellen wollen, dass die Sperre entfernt wird. Hier ist ein Beispiel:

Threading-Gesamtmenge importieren = 0 lock = threading.Lock() def update_total(amount): „““ Aktualisiert die Gesamtsumme um die angegebene Menge „““ globale Gesamtsumme lock.acquire() try: total += Menge schließlich: lock.release( ) print (total) if __name__ == "__main__": for i in range(10): my_thread = threading.Thread(target=update_total, args=(5,)) my_thread.start()

Hier hängen wir einfach das Schloss auf, bevor wir etwas anderes tun. Als nächstes versuchen wir zu aktualisieren gesamt Und Endlich, wir entfernen das Schloss und entfernen das aktuelle gesamt. Wir können vereinfachen diese Aufgabe mit einem Python-Operator namens mit:

Import threading total = 0 lock = threading.Lock() def update_total(amount): „““ Aktualisiert die Gesamtsumme um den angegebenen Betrag „““ globale Gesamtsumme mit Sperre: total += amount print (total) if __name__ == „__main__ ": für i in range(10): my_thread = threading.Thread(target=update_total, args=(5,)) my_thread.start()

Wie Sie sehen, brauchen wir nicht mehr versuchen/endlich, da der vom Operator bereitgestellte Kontextmanager mit, hat das alles für uns getan. Natürlich kann es vorkommen, dass Sie Code schreiben, bei dem mehrere Threads mit Zugriff auf mehrere Funktionen erforderlich sind. Wenn Sie zum ersten Mal mit dem Schreiben beginnen Wettbewerbscode, können Sie etwa Folgendes tun:

Import-Threading total = 0 lock = threading.Lock() def do_something(): lock.acquire() try: print("Sperre in der do_something-Funktion erworben") schließlich: lock.release() print("Sperre in der do_something-Funktion aufgehoben Funktion") return „Etwas erledigt" def do_something_else(): lock.acquire() try: print("Sperre in der Funktion do_something_else erworben") schließlich: lock.release() print("Sperre in der Funktion do_something_else aufgehoben") return „Etwas anderes erledigt“ if __name__ == „__main__“: result_one = do_something() result_two = do_something_else()

Dieser Code funktioniert in diesem Fall gut, setzt jedoch voraus, dass dies der Fall ist mehrere Threads Aufrufen dieser beiden Funktionen. Während ein Thread an Funktionen arbeitet, kann der zweite wiederum die Daten aktualisieren und Sie erhalten ein falsches Ergebnis. Das Problem besteht darin, dass Sie möglicherweise zunächst nicht bemerken, dass mit den Ergebnissen etwas nicht stimmt. Wie findet man eine Lösung für dieses Problem? Schauen wir es uns an. Als erstes können Sie zwei Funktionsaufrufe sperren. Versuchen wir, das obige Beispiel zu aktualisieren, um etwa Folgendes zu erhalten:

Threading importieren total = 0 lock = threading.RLock() def do_something(): mit lock: print("Sperre in der Funktion do_something erworben") print("Sperre in der Funktion do_something aufgehoben") return "Etwas erledigt" def do_something_else (): mit Sperre: print("Sperre in der Funktion do_something_else erworben") print("Sperre in der Funktion do_something_else aufgehoben") return "Etwas anderes erledigt" def main(): mit Sperre: result_one = do_something() result_two = do_something_else () print (result_one) print (result_two) if __name__ == "__main__": main()

Wenn Sie diesen Code ausführen, werden Sie feststellen, dass er einfach hängt. Der Grund ist, dass wir es einfach dem Modul mitteilen Einfädeln ein Schloss aufhängen. Wenn wir also die erste Funktion aufrufen, sieht sie, dass die Sperre bereits hängt und blockiert ist. Dies dauert so lange, bis die Sperre aufgehoben wird, was niemals passieren wird, da dies im Code nicht vorgesehen ist. Gute Entscheidung Verwenden Sie in diesem Fall eine wiedereintretende Sperre. Modul Einfädeln stellt eine als Funktion bereit RLock. Ersetzen Sie einfach die Leitungssperre = Threading.Sperre() auf lock = threading.RLock() und versuchen Sie, den Code erneut auszuführen. Jetzt sollte er es sich verdienen. Wenn Sie den obigen Code ausprobieren, aber Threads hinzufügen möchten, können wir ihn ersetzen Anruf An hauptsächlich auf die folgende Weise:

Wenn __name__ == "__main__": für i in range(10): my_thread = threading.Thread(target=main) my_thread.start()

Dadurch wird die Hauptfunktion in jedem Thread ausgeführt, die wiederum die beiden anderen Funktionen aufruft. Am Ende erhalten Sie eine recht hohe Auszahlung.

Timer

Modul Einfädeln enthält eine sehr praktische Klasse namens Timer, mit dem Sie nach einer bestimmten Zeitspanne eine Aktion auslösen können. Diese Klasse startet ihren eigenen Thread und beginnt von dort aus zu arbeiten Startmethode(), genau wie normale Streams. Sie können den Timer auch mit der Abbruchmethode stoppen. Bitte beachten Sie, dass Sie den Timer abbrechen können, bevor er überhaupt gestartet ist. Ich hatte einmal einen Fall, in dem ich die Kommunikation mit einem von mir gestarteten Unterprozess herstellen musste, aber einen Countdown brauchte. Trotz der Existenz einer Nummer auf verschiedene Arten Lösung für dieses spezielle Problem, meine Lieblingslösung war schon immer die Verwendung Timer-Klasse Threading-Modul In diesem Beispiel werfen wir einen Blick auf die Verwendung des Ping-Befehls. Unter Linux, Ping-Befehl wird funktionieren, bis Sie es töten. Daher wird die Timer-Klasse in der Linux-Welt besonders nützlich. Hier ist ein Beispiel:

Unterprozess aus Threading importieren Import Timer kill = Lambda-Prozess: process.kill() cmd = ["ping", "www.google.com"] ping = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) my_timer = Timer(5, kill, ) versuche: my_timer.start() stdout, stderr = ping.communicate() schließlich: my_timer.cancel() print (str(stdout))

Hier richten wir einfach ein Lambda ein, mit dem wir den Prozess beenden können. Als nächstes beginnen wir mit unserer Arbeit Klingeln und erstellen Sie ein Timer-Objekt. Beachten Sie, dass das erste Argument das Timeout in Sekunden ist, dann die aufzurufende Funktion und das Argument, das an die Funktion übergeben wird. In unserem Fall ist unsere Funktion ein Lambda und wir übergeben ihr eine Liste von Argumenten, wobei die Liste nur ein Element enthält. Wenn Sie diesen Code ausführen, wird er etwa 5 Sekunden lang ausgeführt, bevor das Ping-Ergebnis angezeigt wird.

Andere Thread-Komponenten

Modul Einfädeln Enthält auch Unterstützung für andere Objekte. Sie können zum Beispiel erstellen Semaphor, eines der ältesten Timing-Primitive in der Informatik. Semaphor-Steuerungen interner Zähler, die bei Ihrem Aufruf verringert wird erwerben und erhöht sich, wenn Sie anrufen freigeben. Der Zähler ist so konzipiert, dass er nicht unter Null fallen kann. Wenn es also passiert, dass Sie anrufen erwerben Wenn es Null ist, wird es blockiert.

Ein weiteres nützliches Tool, das im Modul enthalten ist, ist Ereignis. Damit können Sie mithilfe von Signalen eine Kommunikation zwischen zwei Threads herstellen. Im nächsten Artikel werden wir uns Beispiele für die Verwendung von Event ansehen. Schließlich wurde in Python 3.2 das Objekt hinzugefügt Barriere. Dies ist ein Grundelement, das den Thread-Pool verwaltet. Es spielt keine Rolle, wo die Threads warten müssen, bis sie an der Reihe sind. Um die Barriere zu überwinden, muss der Thread die Methode aufrufen Warten(), der blockiert, bis alle Threads den Aufruf getätigt haben. Danach bewegen sich alle Ströme gleichzeitig weiter.

Thread-Kommunikation

Es gibt eine Reihe von Fällen, in denen Sie Threads miteinander in Beziehung setzen müssen. Wie ich bereits erwähnt habe, können Sie verwenden Ereignis für diesen Zweck. Eine bequemere Möglichkeit ist jedoch die Verwendung Warteschlange. In unserem Beispiel verwenden wir beide Methoden! Mal sehen, wie es aussehen wird:

Threading aus Warteschlange importieren Import Warteschlangendefinition Ersteller (Daten, q): „““ Erstellt zu verbrauchende Daten und wartet darauf, dass der Verbraucher die Verarbeitung „““ print(„Daten erstellen und in die Warteschlange stellen“) für das Element in Daten abschließt : evt = threading.Event() q.put((item, evt)) print("Warten auf die Verdoppelung der Daten") evt.wait() def my_consumer(q): """ Verbraucht einige Daten und arbeitet daran In diesem Fall verdoppelt es lediglich die Eingabe „““ während True: data, evt = q.get() print("data Found to be processing: ()".format(data)) processing = data * 2 print (verarbeitet) evt.set() q.task_done() if __name__ == "__main__": q = Queue() data = thread_one = threading.Thread(target=creator, args=(data, q)) thread_two = threading. Thread(target=my_consumer, args=(q,)) thread_one.start() thread_two.start() q.join()

Lasst uns etwas langsamer werden. Erstens haben wir eine Funktion Schöpfer(auch bekannt als Hersteller), mit dem wir die Daten erstellen, mit denen wir arbeiten (oder verwenden) möchten. Als nächstes erhalten wir eine weitere Funktion, mit der wir die aufgerufenen Daten verarbeiten mein_Verbraucher. Die Erstellerfunktion verwendet die Methode Warteschlange Wird put aufgerufen, um Daten zur Warteschlange hinzuzufügen. Anschließend prüft der Verbraucher, ob neue Daten vorhanden sind, und verarbeitet sie, wenn solche vorhanden sind. Warteschlange kümmert sich um das gesamte Schließen und Öffnen von Schlössern, so dass Ihnen persönlich dieses Schicksal erspart bleibt.

In diesem Beispiel haben wir eine Liste mit Werten erstellt, die wir duplizieren möchten. Als nächstes erstellen wir zwei Threads, einen für die Funktion Schöpfer/Produzent, Sekunde für Verbraucher(Verbraucher). Beachten Sie, dass wir ein Objekt übergeben Warteschlange jeden Thread, was geradezu magisch ist, wenn man bedenkt, wie mit Sperren umgegangen wird. Die Warteschlange beginnt damit, dass der erste Thread Daten an den zweiten weitergibt. Wenn der erste Thread einige Daten an die Warteschlange sendet, sendet er diese auch an Ereignis und wartet dann darauf, dass Ereignisse beendet werden. Als nächstes werden in der Consumer-Funktion die Daten verarbeitet und anschließend die Konfigurationsmethode aufgerufen Ereignis, was dem ersten Thread mitteilt, dass die Verarbeitung des zweiten Threads abgeschlossen ist, sodass er fortfahren kann. Die letzte Codezeile ruft auf Join-Methode ein Queue-Objekt, das die Queue anweist, zu warten, bis die Threads die Verarbeitung abgeschlossen haben. Der erste Thread wird beendet, wenn er nichts mehr an die Warteschlange übergeben kann.

Fassen wir es zusammen

Wir haben ziemlich viel Material abgedeckt. Nämlich:

  1. Grundlagen der Arbeit mit dem Threading-Modul
  2. Wie funktionieren Schlösser?
  3. Was ist ein Ereignis und wie kann es verwendet werden?
  4. So verwenden Sie den Timer
  5. Intra-Thread-Kommunikation mit Queue/Event

Jetzt wissen Sie, wie man Threads verwendet und wozu sie gut sind. Ich hoffe, Sie finden in Ihrem eigenen Code Verwendung dafür!


Detaillierte Beschreibung

Protothreads sind eine Art leichter, stapelloser Threads, die für Systeme mit geringem Speicher wie eingebettete Mikrocontrollersysteme oder vernetzte Sensorknoten entwickelt wurden.

Protothreads bieten eine lineare Codeausführung für ereignisgesteuerte Systeme, die in C implementiert sind. Protothreads können mit oder ohne RTOS verwendet werden.

Protothreads stellen einen wartegesperrten Kontext auf einem ereignisgesteuerten System bereit, ohne den Stapelaufwand eines einzelnen Threads. Der Zweck des Threadings besteht darin, eine sequentielle Codeausführung ohne den Einsatz komplexer Zustandsmaschinen oder Multithreading zu implementieren. Protothreads bieten bedingtes Blockieren für die Codeausführung innerhalb des C-Funktionskörpers.

Der Vorteil von Protothreads besteht darin, dass sie einen reinen Ereignismechanismus erreichen, bei dem die lineare Ausführung von Funktionscode entsprechend der gewünschten Bedingung blockiert werden kann. In rein ereignisbasierten Systemen muss die Blockierung manuell implementiert werden, indem die Funktion in zwei Teile geteilt wird – einen Teil für den Code vor dem Blockierungsaufruf und einen zweiten Teil für den Code nach dem Blockierungsaufruf. Dies erschwert die Verwendung von Kontrollstrukturen wie der bedingten if()-Anweisung und while()-Schleifen.

Der Vorteil von Protothreads im Vergleich zu regulären Threads besteht darin, dass ein Protothread keinen separaten Stapel benötigt. Auf Systemen, auf denen Speicher eine knappe Ressource ist, kann die Zuweisung mehrerer Stapel zu einem übermäßigen Speicherverbrauch führen. Im Gegensatz zu einem regulären Stream benötigt ein Protostream je nach verwendeter Architektur 2 bis 12 Bytes zum Speichern des Status.

Anmerkungen: Da Protothreads zwischen blockierenden Aufrufen keinen Kontext auf dem Stapel speichern, Lokale Variablen bleiben nicht erhalten, wenn ein Protothread blockiert ist. Das bedeutet, dass lokale Variablen mit Vorsicht verwendet werden sollten - Wenn Sie irgendwelche Zweifel haben, verwenden Sie keine lokalen Variablen in einem Protothread!

Haupteigenschaften:

  • Es gibt keinen an Assembler gebundenen Code – die Protothread-Bibliothek ist in reinem C geschrieben.
  • Fehleranfällige Funktionen wie longjmp() werden nicht verwendet.
  • Sehr geringer RAM-Speicherverbrauch – nur 2 Byte pro Proto-Thread.
  • Kann sowohl vom Betriebssystem (bei Multithreading) als auch ohne verwendet werden.
  • Bietet blockierendes Warten ohne Multithreading oder Stack-Beteiligung.

Beispiele für Anwendungen, bei denen Sie Folgendes verwenden können:

  • Systeme mit Speicherbeschränkungen.
  • Ereignisgesteuerte Protokollstacks.
  • Sehr kleine eingebettete Systeme.
  • Netzwerkknoten für Sensoren.

Die Protostreams-API besteht aus 4 Grundoperationen. Dies sind Initialisierung PT_INIT() , Ausführung PT_BEGIN() , Blockierung unter Bedingung PT_WAIT_UNTIL() und Exit PT_END() . Darüber hinaus gibt es der Einfachheit halber zwei weitere Funktionen: Blockieren durch umgekehrte Bedingung PT_WAIT_WHILE() und Blockieren auf einem Protothread PT_WAIT_THREAD() .

Siehe auch: Protothread-API-Dokumentation

Autoren

Die Protothread-Bibliothek wurde von Adam Dunkels geschrieben mit Unterstützung von Oliver Schmidt .

Protothreads

Protothreads sind extrem leichte, stapellose Threads, die einen Sperrkontext auf einem ereignisgesteuerten System bereitstellen, ohne den Stapelaufwand jedes Threads. Der Zweck von Protothreads besteht darin, einen sequentiellen Ausführungsfluss ohne komplexe Zustandsmaschinen oder vollständiges Multithreading zu implementieren. Protothreads bieten bedingtes Blockieren für die Codeausführung innerhalb des C-Funktionskörpers.

Auf Systemen mit begrenztem Speicher (wie Mikrocontroller-Systemen) führt herkömmliches Multithreading zu einem zu hohen Speicherverbrauch. Beim herkömmlichen Multithreading benötigt jeder Thread einen separaten Stapel, der viel Speicher beanspruchen kann.

Der Hauptvorteil von Protothreads im Vergleich zu regulären Threads besteht darin, dass ein Protothread sehr leicht ist und keinen separaten Stapel erfordert. Stattdessen verwenden alle Protothreads denselben Systemstapel, und der Kontextwechsel erfolgt über den Stapelrücklauf. Dies ist ein Vorteil auf Systemen, auf denen Speicher eine knappe Ressource ist, da die Zuweisung mehrerer Stapel zu Threads zu einem übermäßigen Speicheraufwand führen kann. Ein Protostream benötigt nur 2 Bytes pro Protostream. Darüber hinaus werden Protothreads in reinem C implementiert und erfordern keinen architekturspezifischen Assemblercode.

Ein Protothread arbeitet innerhalb einer einzelnen C-Funktion und kann keine anderen Funktionen umfassen. Ein Protothread kann normale C-Funktionen aufrufen, ein Blockieren innerhalb der aufgerufenen Funktion ist jedoch nicht möglich. Anstatt innerhalb der aufgerufenen Funktion zu blockieren, wird für jede potenziell blockierende Funktion ein separater Protothread erstellt. Der Vorteil dieses Ansatzes liegt in der expliziten Blockierung: Der Programmierer weiß genau, welche Funktionen die Ausführung blockieren und welche nicht.

Protothreads ähneln asymmetrischen Coroutinen. Der Hauptunterschied zu Coroutinen besteht darin, dass Coroutinen für jede Coroutine einen Stapel verwenden, während Protothreads keinen separaten Stapel für sich selbst verwenden. Der Mechanismus, der Protothreads am ähnlichsten ist, findet sich in Python-Generatoren. Sie haben auch ein stapelloses Design, nur für einen anderen Zweck. Protothreads stellen Kontextsperren innerhalb einer C-Funktion bereit und Python-Generatoren stellen mehrere Ausstiegspunkte aus einer Generatorfunktion bereit.

Lokale Variablen

Anmerkungen: Da Protothreads zwischen blockierenden Aufrufen keinen Kontext auf dem Stapel speichern, werden lokale Variablen nicht gespeichert, wenn der Protothread blockiert. Das bedeutet, dass lokale Variablen mit Vorsicht verwendet werden sollten – im Zweifelsfall keine lokalen Variablen in einem Protothread verwenden!

Planung (Aufgabenplaner) von Protothreads

Ein Protothread wird durch wiederholte Aufrufe der Funktion gesteuert, in der der Protothread ausgeführt wird. Bei jedem Aufruf einer Funktion wird der Protothread ausgeführt, bis er blockiert oder beendet wird. Somit wird die Planung von einer Anwendung durchgeführt, die Protothreads verwendet.

Implementierung

Protothreads werden mithilfe lokaler Fortsetzungen implementiert. Eine lokale Fortsetzung stellt den aktuellen Ausführungsstatus an einer bestimmten Stelle im Programm dar, stellt jedoch keinen Aufrufverlauf oder lokale Variablen bereit. Eine lokale Fortsetzung kann in einer separaten Funktion festgelegt werden, um den Status der Funktion zu erfassen. Sobald eine lokale Fortsetzung erstellt wurde, kann sie fortgesetzt werden, indem der Zustand der Funktion an dem Punkt wiederhergestellt wird, an dem die lokale Fortsetzung erstellt wurde. Notiz Übersetzer: Es klingt sicherlich nach Unsinn, aber etwas wird klar, wenn man sich den Code von Protothread-Makros und deren Verwendung ansieht – zum Beispiel in der Hello-World-Netzwerkanwendung, die auf Protothread aufbaut.

Die lokale Fortsetzung kann auf verschiedene Arten umgesetzt werden:

  1. Verwendung von architekturspezifischem Assembler-Code,
  2. unter Verwendung von Standard-C-Konstrukten oder
  3. Verwendung von Compiler-Erweiterungen.

Die erste Methode funktioniert durch Speichern und Wiederherstellen des Prozessorstatus, ohne Stapelzeiger, und erfordert 16 bis 32 Bytes pro Protothread. Die genaue Speichermenge hängt von der verwendeten Prozessorarchitektur ab.

Die Standard-C-Implementierung erfordert nur 2 Bytes pro Protostream zum Speichern des Status und verwendet die C-Anweisung switch() auf nicht offensichtliche Weise. Diese Implementierung führt jedoch zu einer kleinen Einschränkung für Code, der Protothreads verwendet – der Code selbst kann keine switch()-Anweisungen verwenden.

Bestimmte Compiler verfügen über C-Erweiterungen, die zum Implementieren von Protothreads verwendet werden können. GCC unterstützt Label-Zeiger, die für diesen Zweck verwendet werden können. Bei dieser Implementierung würden Protothreads 4 Byte RAM pro Protothread benötigen.

Makros

Beispiele: dhcpc.c .

Beispiele: dhcpc.c .

Siehe auch: PT_SPAWN() Beispiele: dhcpc.c .

Siehe Definition in der Datei

Was haben ein T-Shirt und ein Computerprogramm gemeinsam? Beide bestehen aus vielen Threads! Während die Fäden in einem T-Shirt den Stoff zusammenhalten, wird C-Faden (wörtlich „Fäden“ oder „Fäden“) verwendet. Betriebssystem Kombinieren Sie alle Programme, um sequentielle oder parallele Aktionen gleichzeitig auszuführen. Jeder Thread in einem Programm identifiziert einen Prozess, der ausgeführt wird, wenn das System (System-Thread C) ihn anfordert. Dies optimiert den Betrieb eines so komplexen Geräts wie Persönlicher Computer und wirkt sich positiv auf dessen Geschwindigkeit und Produktivität aus.

Definition

In der Informatik ist C, ein Thread oder Ausführungsthread, die kleinste Befehlsfolge, die von einem unabhängigen Scheduler gesteuert wird, der normalerweise Teil des Betriebssystems ist.

Threads erhalten normalerweise eine bestimmte Priorität, was bedeutet, dass einige Threads Vorrang vor anderen haben. Sobald der Prozessor die Verarbeitung eines Threads abgeschlossen hat, kann er den nächsten starten, der in der Warteschlange wartet. Normalerweise beträgt die Wartezeit nicht mehr als einige Millisekunden. Computerprogramme Durch die Implementierung von „Multithreading“ können mehrere Threads gleichzeitig ausgeführt werden. Die meisten modernen Betriebssysteme unterstützen C Thread auf Systemebene. Dies bedeutet, dass das System, wenn ein Programm versucht, alle CPU-Ressourcen zu übernehmen, zwangsweise zu anderen Programmen wechselt und das CPU-Unterstützungsprogramm dazu zwingt, die Ressourcen gleichmäßig aufzuteilen.

Der Begriff „Thread“ (C-Thread) kann sich auch auf eine Reihe verwandter Beiträge in einer Online-Diskussion beziehen. Web-Foren bestehen aus vielen Themen oder Threads. Antworten, die als Antwort auf den ursprünglichen Beitrag gepostet wurden, sind Teil desselben Threads. IN Email Ein Thread kann auf eine Reihe von Antworten in Form von Hin- und Her-Befehlen verweisen, die sich auf eine bestimmte Nachricht beziehen, und einen Konversationsbaum strukturieren.

C-Thread-Multithreading unter Windows

In der Computerprogrammierung bezeichnet Single Threading die Verarbeitung jeweils einer Anweisung. Das Gegenteil von Single-Threading ist Multithreading. Beide Begriffe werden in der Community der funktionalen Programmierung häufig verwendet.

Multithreading ähnelt Multitasking, ermöglicht jedoch die gleichzeitige Verarbeitung mehrerer Threads, jedoch nicht mehrerer Prozesse. Da Threads kleiner sind und durch einfachere Anweisungen gesteuert werden, kann es auch innerhalb von Prozessen zu Multithreading kommen.

Beispiele für das C-Thread-Aufgabentool

Ein Multithread-Betriebssystem kann gleichzeitig mehrere Hintergrundaufgaben ausführen, z. B. Dateiänderungen protokollieren, Daten indizieren und Fenster verwalten. Webbrowser, die Multithreading unterstützen, können mehrere Fenster öffnen, während JavaScript und Flash gleichzeitig ausgeführt werden. Wenn das Programm vollständig multithreaded ist, sollten sich die verschiedenen Prozesse nicht gegenseitig beeinflussen, solange der Prozessor über genügend Leistung verfügt, um sie zu verarbeiten.

Wie Multitasking verbessert auch Multithreading die Programmstabilität. Multithreading kann den Absturz eines Programms und Ihres Computers verhindern. Da jeder Thread separat verarbeitet wird, kann ein Fehler in einem von ihnen den Betrieb des PCs nicht stören. Somit kann Multithreading zu weniger Abstürzen im gesamten Betriebssystem führen.

Multitasking

Multitasking verarbeitet mehrere Aufgaben parallel und prägt auch die Prinzipien der Computerbedienung. Der Prozessor kann mehrere Prozesse gleichzeitig mit absoluter Präzision verarbeiten. Es verarbeitet jedoch nur Anweisungen, die ihm von der Software übermittelt werden. Um die Leistungsfähigkeit der CPU voll ausnutzen zu können, muss die Software daher in der Lage sein, mehr als eine Aufgabe gleichzeitig zu bewältigen und außerdem multitaskingfähig zu sein.

Historischer Rückblick

Frühe Betriebssysteme konnten mehrere Programme gleichzeitig ausführen, unterstützten Multitasking jedoch nicht vollständig. Ein Programm könnte alle CPU-Ressourcen verbrauchen, während es einen bestimmten Vorgang ausführt. Grundlegende Betriebssystemaufgaben wie das Kopieren von Dateien verhinderten, dass der Benutzer andere Aufgaben ausführen konnte (z. B. Fenster öffnen oder schließen).

Moderne Betriebssysteme bieten volle Unterstützung für Multitasking – mehrere Softwarelösungen können gleichzeitig ausgeführt werden, ohne sich gegenseitig in ihrer Funktionalität zu beeinträchtigen.

Multitasking verbessert auch die Computerstabilität. Wenn beispielsweise einer der Prozesse ausfällt, hat dies keine Auswirkungen auf die anderen laufende Programme, da der Computer jeden Prozess separat verarbeitet. Dies lässt sich mit dem Vorgang beim Schreiben eines Briefes vergleichen: Wenn Sie sich mitten auf einem Blatt Papier befinden und bereits einen Teil des Textes geschrieben haben, Ihr Webbrowser jedoch unerwartet beendet wird, gehen Ihre bereits erledigten Arbeiten nicht verloren .

Einzel- und Mehrprozessorsysteme

Die Implementierung von Thread- und Prozessortechnologien unterscheidet sich je nach Betriebssystem, aber meistens ist ein Thread eine Komponente eines Prozesses. In einem einzigen Prozess können mehrere Threads gleichzeitig existieren, die Ressourcen ausführen und gemeinsam nutzen. Insbesondere C-Thread-Prozessthreads teilen jederzeit ausführbaren Code und Variablenwerte.

Systeme mit einem einzelnen Prozessor implementieren Zeit-Multithreading: CPU(CPU) wechselt zwischen verschiedenen Threads Software. In einem Multiprozessor- sowie einem Multicore-System werden mehrere Threads parallel ausgeführt, wobei jeder Prozessor oder Kern gleichzeitig einen separaten Thread ausführt.

Arten von Streams

Die Prozessplaner der meisten modernen Betriebssysteme unterstützen direkt sowohl temporäres als auch Multiprozessor-Threading, während der Betriebssystemkernel es Entwicklern ermöglicht, Threads durch Bereitstellung zu verwalten benötigte Funktionenüber die Systemaufrufschnittstelle. Einige Threading-Implementierungen werden als Kernel-Threads bezeichnet, während Lightweight Processes (LWP) eine Art von Threads sind, die dasselbe haben Informationsstand. Auch Softwarelösungen können über User-Space-Threads verfügen, wenn sie mit Timern (Thread-Timer C), Signalen oder anderen Methoden verwendet werden, um ihre eigene Ausführung zu unterbrechen und so eine Art Ad-hoc-Timing durchzuführen.

Threads und Prozesse: Unterschiede

Threads unterscheiden sich von klassischen Multitasking-OS-Prozessen durch folgende Merkmale:

    Prozesse sind normalerweise unabhängig, während Threads als Teilmengen eines Prozesses existieren.

    Prozesse enthalten viel mehr Informationen als Threads;

    Prozesse verfügen über dedizierte Adressräume;

    Prozesse interagieren nur über Systemkommunikationsmechanismen;

    Der Kontextwechsel zwischen Threads in einem Prozess erfolgt schneller als der Kontextwechsel zwischen Prozessen.

Präventive und kollaborative Planung

In Mehrbenutzer-Betriebssystemen ist proaktives Multithreading ein weiter verbreiteter Ansatz zur Steuerung der Ausführungszeit durch Kontextwechsel. Allerdings kann eine proaktive Planung zu unkontrollierter Priorisierung und Ausfällen führen. Im Gegensatz dazu ist kooperatives Multithreading darauf angewiesen, dass Threads die Ausführungskontrolle abgeben. Dies kann zu Problemen führen, wenn ein gemeinsam genutzter Multitasking-Thread durch das Warten auf eine Ressource blockiert wird.

Entwicklung der Technologie

Bis Anfang der 2000er Jahre. auf den meisten Desktop-Computer Es gab nur einen Single-Core-Prozessor, der keine Hardware-Threads unterstützte. Im Jahr 2002 führte Intel die Unterstützung für gleichzeitiges Multithreading auf dem Pentium 4-Prozessor ein, das sogenannte Hyper-Threading. Im Jahr 2005 kamen ein Dual-Core-Prozessor und ein Dual-Core hinzu AMD-Prozessor Athlon 64 X2.

Prozessoren in integrierten Systemen mit höheren Echtzeitanforderungen sind in der Lage, Multithreading durchzuführen, wodurch die Thread-Umschaltzeit verkürzt wird, und sie verwenden für jeden Thread eine eigene Registerdatei.

Modelle

Lassen Sie uns die wichtigsten Implementierungsmodelle auflisten.

1:1 (Threading auf Kernel-Ebene) – Vom Benutzer im Kernel erstellte Threads sind die einfachste mögliche Implementierung von Threads. OS/2 und Win32 verwenden diesen Ansatz nativ, während Linux Thread Join diesen Ansatz über NPTL oder ältere LinuxThreads implementiert. Dieser Ansatz wird auch von Solaris, NetBSD, FreeBSD, macOS und iOS verwendet.

N: 1 (Benutzer-Thread) – Dieses Modell erfordert, dass alle Threads auf Anwendungsebene einem einzelnen geplanten Objekt auf Kernel-Ebene zugeordnet werden. Mit diesem Ansatz kann der Kontextwechsel sehr schnell erfolgen und darüber hinaus sogar auf Kerneln implementiert werden, die kein Threading unterstützen. Einer der Hauptnachteile besteht jedoch darin, dass es keinen Nutzen bringt Hardware-Beschleunigung auf Multithread-Prozessoren oder Computern. Beispiel: Wenn einer der Threads ausgeführt werden muss, wenn eine E/A-Anfrage gestellt wird, wird der gesamte Prozess blockiert und Threading kann nicht verwendet werden. In GNU Portable C wird die Thread-Ausnahme als Threading auf Benutzerebene verwendet.

M:N (Hybridimplementierung) – das Modell ordnet eine Reihe von Anwendungsthreads einer Anzahl von N Kernelzellen oder „virtuellen Prozessoren“ zu. Dies ist ein Kompromiss zwischen Threads auf Kernelebene („1:1“) und Benutzerebene („N:1“). M:N-Streaming-Systeme sind komplexer und erfordern Änderungen sowohl am Kernel als auch am Benutzercode. In einer M:N-Implementierung ist die Thread-Verarbeitungsbibliothek für die Planung von Threads für verfügbare planbare Entitäten verantwortlich. Dadurch wird der Kontext optimal, da Systemaufrufe vermieden werden. Dies erhöht jedoch die Komplexität und die Wahrscheinlichkeit von Inversionen sowie einer suboptimalen Planung ohne umfangreiche (und teure) Koordination zwischen dem Benutzerumgebungs-Scheduler und dem Kernel-Scheduler.

Beispiele für eine Hybridimplementierung sind die Scheduler-Aktivierung, die von der nativen POSIX-Bibliotheksimplementierung von NetBSD verwendet wird (für das M:N-Modell im Gegensatz zum 1:1-Kernel-Implementierungsmodell oder User-Space-Modell).

Leichte Prozesse, die von älteren Versionen des Solaris-Betriebssystems verwendet werden (Std Thread C Toolkit).

Unterstützung von Programmiersprachen

Viele formale Systeme unterstützen die Thread-Funktionalität. Die C- und C++-Implementierungen implementieren diese Technologie und bieten Zugriff auf native APIs für das Betriebssystem. Einige Programmiersprachen sind mehr hohes Level Sprachen wie Java, Python und das .NET Framework machen Threads für Entwickler verfügbar und abstrahieren gleichzeitig bestimmte Unterschiede in der Laufzeitimplementierung von Threads. Andere Spracherweiterungen versuchen ebenfalls, das Konzept der Parallelität und des Threadings vom Entwickler zu abstrahieren. Einige Sprachen sind für sequentielle Parallelität mithilfe von GPUs konzipiert.

Eine Reihe interpretierter Sprachen verfügen über Implementierungen, die Threading und Parallelverarbeitung unterstützen, jedoch aufgrund der globalen Interpretersperre (GIL) keine Thread-Parallelausführung. GIL ist eine vom Interpreter durchgeführte Mutex-Sperre, die verhindern kann, dass Anwendungscode gleichzeitig in zwei oder mehr Threads interpretiert wird, wodurch die Parallelität auf Multicore-Systemen eingeschränkt wird.

Andere Programmierimplementierungen wie Tcl verwenden die Thread-Sleep-C-Erweiterung. Dadurch wird die GIL-Höchstgrenze umgangen, indem ein Modell verwendet wird, bei dem Inhalte und Code explizit zwischen Threads „geteilt“ werden müssen.

Ereignisgesteuerte Anwendungsprogrammiersprachen wie Verilog und die Thread-Sleep-C-Erweiterung verfügen über ein anderes Thread-Modell, das die maximale Anzahl von Threads für die Hardwaresimulation unterstützt.

Praktisches Multithreading

Multithread-Bibliotheken initiieren einen Funktionsaufruf, um einen neuen Thread zu generieren, der den Funktionswert als Parameter verwendet. Anschließend wird ein neuer paralleler Thread erstellt und die laufende Funktion verarbeitet und anschließend zurückgegeben. Programmiersprachen enthalten Thread-Bibliotheken mit globalen Synchronisierungsfunktionen, mit denen Sie fehlerfreies Multithreading mithilfe von Mutexes erstellen und erfolgreich implementieren können. variable Bedingungen, kritische Abschnitte, Monitore und andere Arten der Synchronisierung.

Stichworte: pthreads, pthread_create, pthread_join, EINVAL, ESRCH, EDEADLK, EDEADLOCK, EAGAIN, EPERM, PTHREAD_THREADS_MAX, Übergabe von Argumenten an einen Thread, Rückgabe von Argumenten von einem Thread, pthread_create-Fehler, pthread_join-Fehler, Warten auf einen Thread, Verknüpfen von Threads, Thread-ID, pthreads Beispiel.

Einen Thread erstellen und darauf warten

Schauen wir uns ein einfaches Beispiel an

#enthalten #enthalten #enthalten #enthalten #define ERROR_CREATE_THREAD -11 #define ERROR_JOIN_THREAD -12 #define SUCCESS 0 void* helloWorld(void *args) ( printf("Hallo vom Thread!\n"); return SUCCESS; ) int main() ( pthread_t thread; int status; int status_addr; status = pthread_create(&thread, NULL, helloWorld, NULL); if (status != 0) ( printf("Hauptfehler: Thread kann nicht erstellt werden, Status = %d\n", Status); exit(ERROR_CREATE_THREAD ); ) printf("Hallo von main!\n"); status = pthread_join(thread, (void**)&status_addr); if (status != SUCCESS) ( printf("main error: Thread kann nicht beitreten, Status = %d\n", status); exit(ERROR_JOIN_THREAD); ) printf("verbunden mit Adresse %d\n", status_addr); _getch(); return 0; )

In diesem Beispiel innerhalb des Hauptthreads, in dem die Hauptfunktion wird ein neuer Thread erstellt, in dem die helloWorld-Funktion aufgerufen wird. Die helloWorld-Funktion zeigt eine Begrüßung an. Im Hauptthread ist auch eine Begrüßung abgedruckt. Als nächstes werden die Streams kombiniert.

Mit der Funktion pthread_create wird ein neuer Thread erstellt

Int pthread_create(*ptherad_t, const pthread_attr_t *attr, void* (*start_routine)(void*), void *arg);

Die Funktion erhält als Argumente einen Zeiger auf einen Thread, eine Variable vom Typ pthread_t, in der sie bei erfolgreichem Abschluss die Thread-ID speichert. pthread_attr_t – Thread-Attribute. Wenn Standardattribute verwendet werden, kann NULL übergeben werden. start_routin ist die Funktion, die im neuen Thread ausgeführt wird. arg sind die Argumente, die an die Funktion übergeben werden.

Ein Thread kann viele verschiedene Dinge tun und viele verschiedene Argumente empfangen. Dazu benötigt die Funktion, die in einem neuen Thread gestartet wird, ein Argument vom Typ void*. Aus diesem Grund können Sie alle übergebenen Argumente in eine Struktur einschließen. Sie können einen Wert auch über ein übergebenes Argument zurückgeben.

Bei Erfolg gibt die Funktion 0 zurück. Bei Fehlern können folgende Werte zurückgegeben werden

  • WIEDER– Das System verfügt nicht über die Ressourcen, um einen neuen Thread zu erstellen, oder das System kann keine weiteren Threads erstellen, weil die Anzahl der Threads den Wert PTHREAD_THREADS_MAX überschritten hat (auf einer der zum Testen verwendeten Maschinen ist diese magische Zahl beispielsweise 2019). )
  • EINVAL– falsche Stream-Attribute (als Argument attr übergeben)
  • EPERM– Der aufrufende Thread verfügt nicht über die erforderlichen Rechte zum Festlegen erforderliche Parameter oder Scheduler-Richtlinien.

Gehen wir das Programm durch

#define ERROR_CREATE_THREAD -11 #define ERROR_JOIN_THREAD -12 #define SUCCESS 0

Hier geben wir den Wertesatz an, der zur Behandlung möglicher Fehler erforderlich ist.

Void* helloWorld(void *args) ( printf("Hallo vom Thread!\n"); return SUCCESS; )

Dies ist eine Funktion, die in einem separaten Thread ausgeführt wird. Es werden keine Argumente entgegengenommen. Gemäß dem Standard wird davon ausgegangen, dass ein expliziter Ausstieg aus einer Funktion die Funktion pthread_exit aufruft und der Rückgabewert beim Aufruf der Funktion pthread_join als Status übergeben wird.

Status = pthread_create(&thread, NULL, helloWorld, NULL); if (status != 0) ( printf("Hauptfehler: Thread kann nicht erstellt werden, Status = %d\n", Status); exit(ERROR_CREATE_THREAD); )

Hier wird ein neuer Thread erstellt und sofort ausgeführt. Der Stream erhält keine Attribute oder Argumente. Nachdem der Thread erstellt wurde, erfolgt eine Fehlerprüfung.

Status = pthread_join(thread, (void**)&status_addr); if (status != SUCCESS) ( printf("Hauptfehler: Thread kann nicht beitreten, Status = %d\n", Status); exit(ERROR_JOIN_THREAD); )

Bewirkt, dass der Hauptthread auf den Abschluss des untergeordneten Threads wartet. Funktion

Int pthread_join(pthread_t thread, void **value_ptr);

Verzögert die Ausführung des Thread-Aufrufs (dieser Funktion), bis er ausgeführt wird Fadenfaden. Wenn pthread_join erfolgreich ist, gibt es 0 zurück. Wenn der Thread explizit einen Wert zurückgegeben hat (dies ist derselbe SUCCESS-Wert aus unserer Funktion), wird dieser in der Variablen value_ptr platziert. Mögliche von pthread_join zurückgegebene Fehler

  • EINVAL– Thread zeigt auf einen nicht zusammengeführten Thread
  • ESRCH– Es gibt keinen Thread mit demselben Bezeichner, der in der Thread-Variablen gespeichert ist
  • EDEADLK– Es wurde ein Deadlock (gegenseitige Blockierung) erkannt oder der aufrufende Thread selbst wurde als zusammengeführter Thread angegeben.

Ein Beispiel für die Erstellung von Threads durch Übergabe von Argumenten an sie

Nehmen wir an, wir möchten Daten an einen Stream übergeben und etwas zurückgeben. Nehmen wir an, wir übergeben einen String an einen Stream und geben die Länge dieses Strings vom Stream zurück.

Da eine Funktion nur einen Zeiger vom Typ void empfangen kann, sollten alle Argumente in eine Struktur gepackt werden. Definieren wir einen neuen Strukturtyp:

Typedef struct someArgs_tag ( int id; const char *msg; int out; ) someArgs_t;

Hier ist id die Thread-ID (im Allgemeinen wird sie in unserem Beispiel nicht benötigt), das zweite Feld ist eine Zeichenfolge und das dritte ist die Länge der Zeichenfolge, die wir zurückgeben werden.

Innerhalb der Funktion konvertieren wir das Argument in den gewünschten Typ, drucken die Zeichenfolge aus und fügen die berechnete Länge der Zeichenfolge wieder in die Struktur ein.

Void* helloWorld(void *args) ( someArgs_t *arg = (someArgs_t*) args; int len; if (arg->msg == NULL) ( return BAD_MESSAGE; ) len = strlen(arg->msg); printf(" %s\n", arg->msg); arg->out = len; return SUCCESS; )

Wenn alles gut gelaufen ist, dann geben wir als Status den Wert SUCCESS zurück, und wenn ein Fehler gemacht wurde (in unserem Fall, wenn ein Nullstring übergeben wurde), dann beenden wir mit dem Status BAD_MESSAGE.

In diesem Beispiel erstellen wir 4 Threads. Für 4 Threads benötigen Sie ein Array vom Typ pthread_t der Länge 4, ein Array übergebener Argumente und 4 Strings, die wir übergeben.

Pthread_t-Threads; int-Status; int i; int status_addr; someArgs_t args; const char *messages = ( „Erste“, NULL, „Dritte Nachricht“, „Vierte Nachricht“ );

Zunächst tragen wir die Werte der Argumente ein.

Für (i = 0; i< NUM_THREADS; i++) { args[i].id = i; args[i].msg = messages[i]; }

Für (i = 0; i< NUM_THREADS; i++) { status = pthread_create(&threads[i], NULL, helloWorld, (void*) &args[i]); if (status != 0) { printf("main error: can"t create thread, status = %d\n", status); exit(ERROR_CREATE_THREAD); } }

Dann warten wir auf die Fertigstellung

Für (i = 0; i< NUM_THREADS; i++) { status = pthread_join(threads[i], (void**)&status_addr); if (status != SUCCESS) { printf("main error: can"t join thread, status = %d\n", status); exit(ERROR_JOIN_THREAD); } printf("joined with address %d\n", status_addr); }

Abschließend geben wir auch die Argumente aus, die nun die zurückgegebenen Werte speichern. Beachten Sie, dass eines der Argumente „schlecht“ ist (die Zeichenfolge ist NULL). Hier Vollständiger Code

#enthalten #enthalten #enthalten #enthalten #enthalten #define ERROR_CREATE_THREAD -11 #define ERROR_JOIN_THREAD -12 #define BAD_MESSAGE -13 #define SUCCESS 0 typedef struct someArgs_tag ( int id; const char *msg; int out; ) someArgs_t; void* helloWorld(void *args) ( someArgs_t *arg = (someArgs_t*) args; int len; if (arg->msg == NULL) ( return BAD_MESSAGE; ) len = strlen(arg->msg); printf(" %s\n", arg->msg); arg->out = len; return SUCCESS; ) #define NUM_THREADS 4 int main() ( pthread_t threads; int status; int i; int status_addr; someArgs_t args; const char * message = ("Erste", NULL, "Dritte Nachricht", "Vierte Nachricht" ); for (i = 0; i< NUM_THREADS; i++) { args[i].id = i; args[i].msg = messages[i]; } for (i = 0; i < NUM_THREADS; i++) { status = pthread_create(&threads[i], NULL, helloWorld, (void*) &args[i]); if (status != 0) { printf("main error: can"t create thread, status = %d\n", status); exit(ERROR_CREATE_THREAD); } } printf("Main Message\n"); for (i = 0; i < NUM_THREADS; i++) { status = pthread_join(threads[i], (void**)&status_addr); if (status != SUCCESS) { printf("main error: can"t join thread, status = %d\n", status); exit(ERROR_JOIN_THREAD); } printf("joined with address %d\n", status_addr); } for (i = 0; i < NUM_THREADS; i++) { printf("thread %d arg.out = %d\n", i, args[i].out); } _getch(); return 0; }

Machen Sie es mehrmals. Beachten Sie, dass die Reihenfolge, in der Threads ausgeführt werden, nicht deterministisch ist. Durch die Ausführung des Programms können Sie jedes Mal eine andere Ausführungsreihenfolge erhalten.



Wird geladen...
Spitze