Thread-Klassifizierung
Threads können entsprechend ihren Planern in Threads auf Benutzerebene und Threads auf Kernebene unterteilt werden.
(1) Thread auf Benutzerebene
Thread auf Benutzerebene löst hauptsächlich das Problem des Kontextwechsels. Sein Planungsalgorithmus und sein Planungsprozess werden alle vom Benutzer festgelegt, und zur Laufzeit ist keine spezielle Kernel-Unterstützung erforderlich . . Dabei stellt das Betriebssystem häufig eine User-Space-Thread-Bibliothek bereit, die Thread-Erstellung, -Planung, -Abbruch und andere Funktionen bereitstellt, während der Kernel weiterhin nur den Prozess verwaltet. Wenn ein Thread in einem Prozess einen blockierenden Systemaufruf aufruft, wird der Prozess, einschließlich aller anderen Threads im Prozess, ebenfalls blockiert. Der Hauptnachteil dieser Art von Thread auf Benutzerebene besteht darin, dass er die Vorteile von Multiprozessoren bei der Planung mehrerer Threads in einem Prozess nicht nutzen kann.
(2) Thread auf Kernebene
Diese Art von Thread ermöglicht die Planung von Threads in verschiedenen Prozessen nach derselben relativen Prioritätsplanungsmethode, sodass die Parallelitätsvorteile von Multiprozessoren genutzt werden können.
Die meisten Systeme verwenden jetzt die Methode der Koexistenz von Threads auf Benutzerebene und Threads auf Kernebene. Ein Thread auf Benutzerebene kann einem oder mehreren Threads auf Kernebene entsprechen, was einem „Eins-zu-Eins“- oder „Viele-zu-Eins“-Modell entspricht. Dies erfüllt nicht nur die Anforderungen von Multiprozessorsystemen, sondern minimiert auch den Planungsaufwand.
Die Linux-Thread-Implementierung wird außerhalb des Kerns durchgeführt und der Kern stellt die Schnittstelle do_fork() zum Erstellen eines Prozesses bereit. Der Kernel stellt zwei Systemaufrufe clone() und fork() zur Verfügung, die letztendlich die Kernel-API do_fork() mit unterschiedlichen Parametern aufrufen. Wenn Sie Threads implementieren möchten, ist dies natürlich ohne Kernunterstützung für gemeinsam genutzte Datensegmente mit mehreren Prozessen (eigentlich leichtgewichtigen Prozessen) nicht möglich. Daher stellt do_fork() viele Parameter bereit, einschließlich CLONE_VM (gemeinsam genutzter Speicherraum) und CLONE_FS (gemeinsam genutzte Datei). Systeminformationen), CLONE_FILES (gemeinsam genutzte Dateideskriptortabelle), CLONE_SIGHAND (gemeinsam genutzte Signalhandle-Tabelle) und CLONE_PID (gemeinsam genutzte Prozess-ID, nur gültig für den Kernprozess, also Prozess 0). Bei Verwendung des fork-Systemaufrufs ruft der Kernel do_fork() auf, ohne gemeinsame Attribute zu verwenden. Der Prozess verfügt über eine unabhängige Ausführungsumgebung und verwendet Wenn pthread_create () zum Erstellen eines Threads verwendet wird, werden alle diese Attribute schließlich auf den Aufruf von __clone () festgelegt und alle diese Parameter werden an do_fork () im Kern übergeben. Der so erstellte „Prozess“ verfügt nur über eine gemeinsame Betriebsumgebung Der Stapel ist unabhängig und wird von __clone() übergeben.
Linux-Threads existieren in Form von leichtgewichtigen Prozessen innerhalb des Kerns mit unabhängigen Prozesstabelleneinträgen, und alle Erstellungs-, Synchronisierungs-, Lösch- und anderen Vorgänge werden in der pthread-Bibliothek außerhalb des Kerns ausgeführt. pthread Die Bibliothek verwendet einen Verwaltungsthread (__pthread_manager(), jeder Prozess ist unabhängig und einzigartig), um die Erstellung und Beendigung von Threads zu verwalten, Threads Thread-IDs zuzuweisen und Thread-bezogene Signale (z. B. Abbrechen) zu senden, während der Hauptthread ( pthread_create()) Der Aufrufer übergibt die Anforderungsinformationen über die Pipeline an den Verwaltungsthread.
Hauptfunktionsbeschreibung
1. Thread-Erstellung und -Beendigung
pthread_create Thread-Erstellungsfunktion
int pthread_create (pthread_t * thread_id,__const
pthread_attr_t * __attr,void *(*__start_routine) (void *),void *__restrict
__arg);
Der erste Parameter der Thread-Erstellungsfunktion ist ein Zeiger auf die Thread-ID, der zweite Parameter wird zum Festlegen der Thread-Attribute verwendet, der dritte Parameter ist die Startadresse der Thread-Ausführungsfunktion und Der letzte Parameter sind die Parameter zum Ausführen der Funktion. Hier unser Funktionsthread Es sind keine Parameter erforderlich, daher wird der letzte Parameter auf einen Nullzeiger gesetzt. Wir setzen außerdem den zweiten Parameter auf einen Nullzeiger, wodurch ein Thread mit Standardattributen generiert wird. Wenn die Thread-Erstellung erfolgreich ist, gibt die Funktion 0 zurück, wenn nicht 0 Dies bedeutet, dass die Thread-Erstellung fehlgeschlagen ist und der häufige Fehlerrückgabecode EAGAIN ist und EINVAL. Ersteres bedeutet, dass das System beispielsweise die Erstellung neuer Threads einschränkt, wenn die Anzahl der Threads zu groß ist. Letzteres bedeutet, dass der durch den zweiten Parameter dargestellte Thread-Attributwert unzulässig ist. Nachdem der Thread erfolgreich erstellt wurde, führt der neu erstellte Thread die durch Parameter drei und Parameter vier bestimmte Funktion aus, und der ursprüngliche Thread führt weiterhin die nächste Codezeile aus.
pthread_join-Funktion zum Warten auf das Ende eines Threads.
Der Funktionsprototyp ist: int pthread_join (pthread_t __th, void
**__thread_return)
Der erste Parameter ist die Kennung des Threads, auf den gewartet wird, und der zweite Parameter ist ein benutzerdefinierter Zeiger, der zum Speichern des Rückgabewerts des Threads, auf den gewartet wird, verwendet werden kann. Diese Funktion ist eine Thread-blockierende Funktion. Die Funktion, die sie aufruft, wartet, bis der wartende Thread endet. Wenn die Funktion zurückkehrt, werden die Ressourcen des wartenden Threads wiederhergestellt. Ein Thread kann nur von einem Thread beendet werden und sollte sich im verbindungsfähigen Zustand (nicht getrennt) befinden.
pthread_exit
Funktion
Es gibt zwei Möglichkeiten, einen Thread zu beenden: Wenn die vom Thread ausgeführte Funktion endet, endet auch der Thread, der sie aufgerufen hat.
Die andere Möglichkeit besteht darin, die Funktion pthread_exit zu verwenden
zu erreichen. Sein Funktionsprototyp ist: void pthread_exit (void *__retval) Der einzige Parameter ist der Rückgabecode der Funktion, solange pthread_join
Der zweite Parameter thread_return in
Nicht NULL, dieser Wert wird an thread_return übergeben. Als letztes ist zu beachten, dass nicht mehrere Threads auf einen Thread warten können. Andernfalls kehrt der erste Thread, der das Signal empfängt, erfolgreich zurück und der Rest ruft pthread_join auf
Der Thread gibt den Fehlercode ESRCH zurück.
2. Thread-Eigenschaften
Die Eigenschaften des zweiten Parameterthreads der Funktion pthread_create. Setzen Sie diesen Wert auf NULL, d. h. verwenden Sie die Standardattribute. Viele Attribute des Threads können geändert werden. Zu diesen Eigenschaften gehören hauptsächlich Bindungseigenschaften, Trennungseigenschaften, Stapeladresse, Stapelgröße und Priorität. Die Standardattribute des Systems sind unverbindlich, nicht losgelöst und der Standardwert ist 1M.
Stack und die gleiche Prioritätsstufe wie der übergeordnete Prozess. Im Folgenden werden zunächst die Grundkonzepte gebundener Attribute und losgelöster Attribute erläutert.
Bindungsattribut: Linux verwendet einen „Eins-zu-Eins“-Thread-Mechanismus, das heißt, ein Benutzer-Thread entspricht einem Kernel-Thread. Das Bindungsattribut bedeutet, dass ein Benutzerthread fest einem Kernel-Thread zugeordnet ist, da die Planung von CPU-Zeitscheiben für Kernel-Threads erfolgt.
(d. h. ein leichter Prozess), sodass Threads mit Bindungsattributen sicherstellen können, dass bei Bedarf immer ein entsprechender Kernel-Thread vorhanden ist. Im Gegensatz dazu bedeutet das nicht bindende Attribut, dass die Beziehung zwischen Benutzer-Threads und Kernel-Threads nicht immer festgelegt ist, sondern vom System gesteuert und zugewiesen wird.
Ablösungsattribut: Das Ablösungsattribut wird verwendet, um zu bestimmen, wie sich ein Thread selbst beendet. Im Falle einer Nichttrennung werden beim Ende eines Threads die von ihm belegten Systemressourcen nicht freigegeben, d. h., es findet keine echte Beendigung statt. Erst wenn die Funktion pthread_join() zurückkehrt, kann der erstellte Thread die von ihm belegten Systemressourcen freigeben. Bei losgelösten Attributen werden die von ihnen belegten Systemressourcen sofort freigegeben, wenn ein Thread endet.
Hier ist Folgendes zu beachten: Wenn Sie das Trennungsattribut eines Threads festlegen und dieser Thread sehr schnell läuft, befindet er sich wahrscheinlich in pthread_create
Die Funktion wird beendet, bevor sie zurückkehrt. Nach ihrer Beendigung werden die Thread-Nummer und die Systemressourcen möglicherweise an andere Threads übergeben. Zu diesem Zeitpunkt erhält der Thread, der pthread_create aufruft, die falsche Thread-Nummer.
Bindungsattribute festlegen:
int pthread_attr_init(pthread_attr_t *attr)
int
pthread_attr_setscope(pthread_attr_t *attr, int Scope)
int
pthread_attr_getscope(pthread_attr_t *tattr, int
*scope)
scope: PTHREAD_SCOPE_SYSTEM: Bindung, dieser Thread konkurriert mit allen Threads im System
PTHREAD_SCOPE_PROCESS: Ungebunden, dieser Thread konkurriert mit anderen Threads im Prozess
Detach-Attribut festlegen:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
int
pthread_attr_getdetachstate(const pthread_attr_t *tattr,int
*detachstate)
detachstate PTHREAD_CREATE_DETACHED: PTHREAD trennen
_CREATE_JOINABLE: nicht getrennt
Planungsrichtlinie festlegen:
int pthread_attr_setschedpolicy(pthread_attr_t * tattr, int Policy)
int
pthread_attr_getschedpolicy(pthread_attr_t * tattr, int *policy)
Richtlinie
SCHED_FIFO: First in, first out SCHED_RR: Schleife SCHED_OTHER: Implementierungsdefinierte Methode
Priorität festlegen:
int pthread_attr_setschedparam (pthread_attr_t *attr, struct sched_param
*param)
int pthread_attr_getschedparam (pthread_attr_t *attr, struct
sched_param *param)
3. Thread-Zugriffskontrolle
1) Mutex-Sperre (Mutex)
erreicht die Synchronisation zwischen Threads über den Sperrmechanismus. Es darf jeweils nur ein Thread einen kritischen Codeabschnitt ausführen.
1 int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t
*mutexattr);
2 int pthread_mutex_lock(pthread_mutex_t *mutex);
3 int
pthread_mutex_unlock(pthread_mutex_t *mutex);
4 int
pthread_mutex_destroy(pthread_mutex_t *mutex);
(1) Initialisieren Sie zuerst die Sperre init() oder weisen Sie pthread_mutex_t statisch zu
mutex=PTHREAD_MUTEX_INITIALIER
(2) Sperren, Sperren, Trylock, Sperrblock wartet auf die Sperre, Trylock gibt EBUSY sofort zurück
(3) Entsperren, Entsperren muss sich im gesperrten Zustand befinden und durch den Sperrthread entsperrt werden
(4) Sperre löschen und zerstören (die Sperre muss zu diesem Zeitpunkt entsperrt werden, sonst wird EBUSY zurückgegeben)
Mutex ist in zwei Typen unterteilt: rekursiv und nicht rekursiv. Dies ist der Name von POSIX . Außerdem ist der Name Reentrant
Nicht wiedereintretend. Es gibt keinen Unterschied zwischen diesen beiden Mutexes, wenn sie als Inter-Thread-Synchronisationstools verwendet werden. Ihr einziger Unterschied besteht darin, dass derselbe Thread wiederholt rekursiv mutexieren kann
Der Mutex ist gesperrt, aber der nicht rekursive Mutex kann nicht wiederholt gesperrt werden.
Sperren.
Nicht rekursiver Mutex wird bevorzugt, definitiv nicht aus Leistungsgründen, sondern um die Designabsicht widerzuspiegeln. nicht rekursiv und rekursiv
Der Leistungsunterschied zwischen den beiden ist eigentlich nicht groß, da ein Zähler weniger verwendet wird und ersterer nur etwas schneller ist. Wiederholen Sie den nicht rekursiven Mutex mehrmals im selben Thread
Das Sperren führt meiner Meinung nach sofort zu einem Deadlock. Es kann uns helfen, über die Sperranforderungen des Codes nachzudenken und Probleme frühzeitig zu erkennen (während der Codierungsphase). zweifellos rekursiver Mutex
Es ist bequemer zu verwenden, da Sie sich keine Sorgen über eine Thread-Sperre selbst machen müssen. Ich denke, das ist der Grund, warum Java und Windows standardmäßig rekursiven Mutex bereitstellen. (Java
Die intrinsische Sperre, die mit der Sprache einhergeht, ist reentrant und gleichzeitig
Die Bibliothek stellt ReentrantLock bereit, und CRITICAL_SECTION von Windows ist ebenfalls wiedereintrittsfähig. Es scheint, dass keiner von ihnen leichte, nicht rekursive Funktionen bietet
Mutex. )
2) Bedingungsvariable (cond)
Ein Mechanismus, der von Threads gemeinsam genutzte globale Variablen zur Synchronisierung verwendet.
1 int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
2 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
3
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,const
timespec *abstime);
4 int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
6 int
pthread_cond_broadcast(pthread_cond_t *cond); //Alle Threads entsperren
(1) init() oder pthread_cond_t
cond=PTHREAD_COND_INITIALIER; Setzen Sie das Attribut auf NULL
(2) Warten Sie, bis die Bedingung erfüllt ist.
pthread_cond_wait,pthread_cond_timedwait.
wait() gibt die Sperre frei und blockiert das Warten darauf, dass die Bedingungsvariable wahr ist.
timedwait() legt die Wartezeit fest, aber es gibt immer noch kein Signal, und gibt ETIMEOUT zurück (die Sperrung stellt sicher, dass nur ein Thread wartet )
(3) Aktivieren Sie die Bedingungsvariable :pthread_cond_signal,pthread_cond_broadcast (aktivieren Sie alle wartenden Threads)
(4) Löschen Sie Bedingungsvariablen:destroy;
Es gibt keinen wartenden Thread, andernfalls wird EBUSY
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);zurückgegeben. Diese beiden Funktionen müssen innerhalb des Mutex-Sperrbereichs verwendet werden. Rufen Sie pthread_cond_signal() auf Beim Freigeben eines bedingt blockierten Threads hat der Aufruf von pthread_cond_signal() keine Auswirkung, wenn kein Thread basierend auf der Bedingungsvariablen blockiert ist. Für Windows, beim Anruf Wenn SetEvent die Bedingung „Auto-Reset-Ereignis“ auslöst und kein Thread durch die Bedingung blockiert wird, funktioniert diese Funktion weiterhin und die Bedingungsvariable befindet sich im ausgelösten Zustand. Producer-Consumer-Probleme unter Linux (mit Mutexes und Bedingungsvariablen):
#include <stdio.h> #include <stdlib.h> #include <time.h> #include "pthread.h" #define BUFFER_SIZE 16 struct prodcons { int buffer[BUFFER_SIZE]; pthread_mutex_t lock; //mutex ensuring exclusive access to buffer int readpos,writepos; //position for reading and writing pthread_cond_t notempty; //signal when buffer is not empty pthread_cond_t notfull; //signal when buffer is not full }; //initialize a buffer void init(struct prodcons* b) { pthread_mutex_init(&b->lock,NULL); pthread_cond_init(&b->notempty,NULL); pthread_cond_init(&b->notfull,NULL); b->readpos = 0; b->writepos = 0; } //store an integer in the buffer void put(struct prodcons* b, int data) { pthread_mutex_lock(&b->lock); //wait until buffer is not full while((b->writepos+1)%BUFFER_SIZE == b->readpos) { printf("wait for not full\n"); pthread_cond_wait(&b->notfull,&b->lock); } b->buffer[b->writepos] = data; b->writepos++; b->writepos %= BUFFER_SIZE; pthread_cond_signal(&b->notempty); //signal buffer is not empty pthread_mutex_unlock(&b->lock); } //read and remove an integer from the buffer int get(struct prodcons* b) { int data; pthread_mutex_lock(&b->lock); //wait until buffer is not empty while(b->writepos == b->readpos) { printf("wait for not empty\n"); pthread_cond_wait(&b->notempty,&b->lock); } data=b->buffer[b->readpos]; b->readpos++; b->readpos %= BUFFER_SIZE; pthread_cond_signal(&b->notfull); //signal buffer is not full pthread_mutex_unlock(&b->lock); return data; } #define OVER -1 struct prodcons buffer; void * producer(void * data) { int n; for(n=0; n<50; ++n) { printf("put-->%d\n",n); put(&buffer,n); } put(&buffer,OVER); printf("producer stopped\n"); return NULL; } void * consumer(void * data) { int n; while(1) { int d = get(&buffer); if(d == OVER) break; printf("get-->%d\n",d); } printf("consumer stopped\n"); return NULL; } int main() { pthread_t tha,thb; void * retval; init(&buffer); pthread_creare(&tha,NULL,producer,0); pthread_creare(&thb,NULL,consumer,0); pthread_join(tha,&retval); pthread_join(thb,&retval); return 0; }3) Semaphoren
Wie Prozesse können auch Threads Semaphoren verwenden, um die Kommunikation zu implementieren, wenn auch leichtgewichtig.
#include <semaphore.h> int sem_init(sem_t *sem , int pshared, unsigned int value);Dies dient dazu, das durch sem angegebene Semaphor zu initialisieren, seine Freigabeoption festzulegen (Linux unterstützt nur 0, was bedeutet, dass es ein lokales Semaphor des aktuellen Prozesses ist) und es dann zu geben Anfangswert VALUE.
int sem_wait(sem_t *sem); //给信号量减1,对一个值为0的信号量调用sem_wait,这个函数将会等待直到有其它线程使它不再是0为止。 int sem_post(sem_t *sem); //给信号量的值加1 int sem_destroy(sem_t *sem);Die Funktion dieser Funktion besteht darin, das Semaphor zu bereinigen, nachdem wir es verwendet haben. Geben Sie alle Ressourcen zurück, die Sie besitzen. Verwenden Sie Semaphoren, um Produzenten und Konsumenten zu implementieren: Hier werden vier Semaphoren verwendet, von denen zwei Semaphoren, besetzt und leer, verwendet werden, um die Synchronisation zwischen Produzenten- und Konsumenten-Threads zu lösen besteht darin, dass pmut für den gegenseitigen Ausschluss zwischen mehreren Produzenten und cmut für den gegenseitigen Ausschluss zwischen mehreren Verbrauchern verwendet wird. Unter diesen wird leer auf N (die Anzahl der Raumelemente des begrenzten Puffers) initialisiert, belegt wird auf 0 initialisiert und pmut und cmut werden auf 1 initialisiert. Referenzcode:
#define BSIZE 64 typedef struct { char buf[BSIZE]; sem_t occupied; sem_t empty; int nextin; int nextout; sem_t pmut; sem_t cmut; }buffer_t; buffer_t buffer; void init(buffer_t * b) { sem_init(&b->occupied, 0, 0); sem_init(&b->empty,0, BSIZE); sem_init(&b->pmut, 0, 1); sem_init(&b->cmut, 0, 1); b->nextin = b->nextout = 0; } void producer(buffer_t *b, char item) { sem_wait(&b->empty); sem_wait(&b->pmut); b->buf[b->nextin] = item; b->nextin++; b->nextin %= BSIZE; sem_post(&b->pmut); sem_post(&b->occupied); } char consumer(buffer_t *b) { char item; sem_wait(&b->occupied); sem_wait(&b->cmut); item = b->buf[b->nextout]; b->nextout++; b->nextout %= BSIZE; sem_post(&b->cmut); sem_post(&b->empty); return item; }