[Linux]Multithread-Programmierung

[Linux]Multithread-Programmierung

Unter dem Linux-Betriebssystem gibt es keine echten Threads, sondern Threads, die durch Lightweight-Prozesse (LWP) im Prozess simuliert werden. Daher stellt das Linux-Betriebssystem nur Systemschnittstellen für Prozessoperationen bereit. Zur Vereinfachung der Benutzerbedienung bietet das Linux-Betriebssystem jedoch eine native Thread-Bibliothek auf Benutzerebene. Die native Thread-Bibliothek kapselt die Systemschnittstelle, sodass Benutzer Thread-Vorgänge so ausführen können, als wären sie echte Threads Thread-Bibliothek wird verwendet. Beim Kompilieren des Codes müssen Sie die Thread-Bibliothek zum Verknüpfen angeben.

pthread_create-Funktion

pthread_createFunktion wird zum Erstellen von Threads verwendet.

//pthread_create函数所在的头文件和函数声明
#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • **Thread-Parameter:** Gibt die Thread-ID zurück

  • **attr-Parameter: **Legen Sie die Attribute des Threads fest. Attr ist NULL, um die Standardattribute zu verwenden.

  • start_routine-Parameter : Es handelt sich um eine Funktionsadresse, die Funktion, die nach dem Start des Threads ausgeführt werden soll.

  • **arg-Parameter:**Der an die Thread-Startfunktion übergebene Parameter

  • **Rückgabewert:** Gibt bei Erfolg 0 und bei Fehler einen Fehlercode zurück. ( Da Threads denselben Adressraum nutzen, wird der Fehlercode nicht durch Setzen der globalen Variablen errno aufgezeichnet. )

Schreiben Sie zum Testen den folgenden Code:

#include <iostream>
#include <unistd.h>
#include <pthread.h>

using namespace std;

void *thread_run(void *args)
{
    
    
    while(true)
    {
    
    
        cout << "new pthread running" << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    
    
    pthread_t t;
    int n = pthread_create(&t, nullptr, thread_run, nullptr);
    if (n!=0) cerr << "new thread error" << endl; 

    while(true)
    {
    
    
        cout << "main pthread running, new pthread id: " << t << endl;
        sleep(1);
    }
    return 0;
}

Kompilieren Sie den Code und führen Sie ihn aus, um die Ergebnisse anzuzeigen:

pthreadTest1

Darüber hinaus können Sie die Anweisungen des Linux-Betriebssystems verwenden, ps -aL | head -1 && ps -aL | grep 进程名um Threads anzuzeigen:

pthreadTest2

Der Thread mit der gleichen LWPid und PID ist der Hauptthread, der Rest sind neue Threads.

pthread_join-Funktion

Ähnlich wie bei dem Prozess muss der Thread nach dem Beenden des Threads unter dem Linux-Betriebssystem auch auf die Wiederverwertung des neuen Threads warten, sodass eine Funktion bereitgestellt wird pthread_join.

//pthread_join函数所在的头文件和函数声明
 #include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
  • Thread-Parameter: Die neue Thread-ID, auf die gewartet und recycelt werden soll. ( pthread_createDer Ausgabeparameter-Thread, wenn die Funktion einen neuen Thread erstellt)
  • retval-Parameter: Empfangen Sie den Rückgabewert des Thread-Exits als Ausgabeparameter.
  • **Rückgabewert:** Gibt bei Erfolg 0 und bei Fehler einen Fehlercode zurück.
  • Wenn der Thread abgebrochen wird, wird der Parameter retval empfangen PTHREAD_CANCELED ((void *) -1).

Hinweis: Da Thread-Ausnahmen Signale erzeugen, die direkt zum Beenden des Prozesses führen, besteht keine Notwendigkeit, die Ausnahmeerkennung in Betracht zu ziehen, wenn der Thread auf die Wiederverwendung wartet.

Schreiben Sie zum Testen den folgenden Code:

#include <iostream>
#include <unistd.h>
#include <pthread.h>

#define NUM 10

using namespace std;

void *thread_run(void *args)
{
    
    
    while(true)
    {
    
    
        cout << "new pthread running" << endl;
        sleep(4);
        break;
    }
    return (void*)0;//返回值为0的数据
}

int main()
{
    
    
    pthread_t tid[NUM];
    for (int i = 0; i < NUM; i++)
    {
    
    
        int n = pthread_create(tid+i, nullptr, thread_run, nullptr);
        if (n!=0) cerr << "new thread error, thread-" << i << endl; 
    }

    void *ret = nullptr;
    for (int i = 0; i < NUM; i++)
    {
    
    
        int m = pthread_join(tid[i], &ret);//等待回收新线程
        if (m!=0) cerr << "new thread join error, thread-" << i << endl; 
        cout << "thread-" << i << "quit, ret: " << (uint64_t)ret << endl;//打印新线程退出返回值
    }

    cout << "all thread quit" << endl;//打印说明所有线程退出

    return 0;
}

Kompilieren Sie den Code und führen Sie ihn aus, um die Ergebnisse anzuzeigen. Verwenden Sie Anweisungen, um den Prozess zu erkennen, wenn er ausgeführt wird while :; do ps -aL | head -1 && ps -aL | grep 进程名; sleep 1; done:

pthreadTest3

Sie können sehen, dass der Thread beendet wird und Daten mit einem Rückgabewert von 0 zurückgibt, und der Haupt-Thread-Aufruf pthread_joinkann den Rückgabewert erfolgreich empfangen.

pthread_exit-Funktion

So beenden Sie Threads unter dem Linux-Betriebssystem:

  1. Die Thread-Ausführungsfunktion endet und return kehrt zurück
  2. Rufen Sie phread_exitdie Funktion auf, um den Thread zu verlassen

Hinweis: Unabhängig davon, ob der Thread die Funktion ausführt, zurückgibt oder phread_exitdie Funktion aufruft, um den Thread zu verlassen, void *wird letztendlich eine Art Rückgabewert an den Hauptthread zurückgegeben.

//pthread_exit函数所在的头文件和函数声明
#include <pthread.h>

void pthread_exit(void *retval);
  • retval-Parameter: Wird als Rückgabewert der Thread-Beendigung an den Hauptthread zurückgegeben.

Schreiben Sie zum Testen den folgenden Code:

#include <iostream>
#include <unistd.h>
#include <pthread.h>

#define NUM 10

using namespace std;

void *thread_run(void *args)
{
    
    
    while(true)
    {
    
    
        cout << "new pthread running" << endl;
        sleep(4);
        pthread_exit((void*)1);
    }
}

int main()
{
    
    
    pthread_t tid[NUM];
    for (int i = 0; i < NUM; i++)
    {
    
    
        int n = pthread_create(tid+i, nullptr, thread_run, nullptr);
        if (n!=0) cerr << "new thread error, thread-" << i << endl; 
    }

    void *ret = nullptr;
    for (int i = 0; i < NUM; i++)
    {
    
    
        int m = pthread_join(tid[i], &ret);//等待回收新线程
        if (m!=0) cerr << "new thread join error, thread-" << i << endl; 
        cout << "thread-" << i << "quit, ret: " << (uint64_t)ret << endl;//打印新线程退出返回值
    }

    cout << "all thread quit" << endl;//打印说明所有线程退出

    return 0;
}

Kompilieren Sie den Code und führen Sie ihn aus, um die Ergebnisse anzuzeigen:

pthreadTest4

Sie können sehen, dass der Thread beendet wird phread_exitund Daten mit dem Wert 1 zurückgibt und der Haupt-Thread-Aufruf pthread_joinden Rückgabewert erfolgreich empfangen kann.

pthread_cancel-Funktion

pthread_cancelDie Funktion kann den laufenden Thread abbrechen.

//pthread_cancel函数所在的头文件和函数声明
#include <pthread.h>

int pthread_cancel(pthread_t thread);
  • Thread-Parameter: die Thread-ID, die abgebrochen werden soll.
  • **Rückgabewert:** Gibt bei Erfolg 0 und bei Fehler einen Fehlercode zurück.

Schreiben Sie zum Testen den folgenden Code:

#include <iostream>
#include <unistd.h>
#include <pthread.h>

using namespace std;

void *thread_run(void *args)
{
    
    
    while (true)//新线程死循环执行代码
    {
    
    
        cout << "new thread running" << endl;
        sleep(1);
    }
    pthread_exit((void *)11);
}

int main()
{
    
    
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, nullptr);

    int cnt = 3;
    while (true)
    {
    
    
        sleep(1);
        if ((cnt--) == 0)
        {
    
    
            pthread_cancel(t);//取消新线程
            break;
        }
    }
    sleep(2);
    void *ret = nullptr;
    pthread_join(t, &ret);//等待回收新线程
    cout << "new thread quit " << "ret: " << (int64_t)ret << endl;
    return 0;
}

Kompilieren Sie den Code und führen Sie ihn aus, um die Ergebnisse anzuzeigen:

pthreadTest5

Der neue Thread in der Endlosschleife wurde vom Hauptthread abgebrochen. Nachdem der neue Thread abgebrochen wurde, pthread_joinerhielt die Hauptthread-Funktion PTHREAD_CANCELED ((void *) -1).

pthread_self-Funktion

pthread_selfDie Funktion wird verwendet, um die Thread-ID des aktuellen Threads abzurufen.

//pthread_self函数所在的头文件和函数声明
#include <pthread.h>

pthread_t pthread_self(void);
  • Rückgabewert: Gibt die Thread-ID des aufrufenden Threads zurück.

Schreiben Sie zum Testen den folgenden Code:

#include <iostream>
#include <pthread.h>

using namespace std;


void *thread_run(void *args)
{
    
    
    pthread_t tid = pthread_self();
    cout << "i am new thread, my thread id: " << tid << endl;
    return nullptr;
}

int main()
{
    
    
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, nullptr);
    pthread_join(t, nullptr);
    cout << "new thread id: " << t << endl;
    return 0;
}

Kompilieren Sie den Code und führen Sie ihn aus, um die Ergebnisse anzuzeigen:

Bild-20230923174750463

pthread_detach-Funktion

Nachdem der neu erstellte Thread beendet wurde, muss er standardmäßig ausgeführt werden pthread_join. Andernfalls können die Ressourcen nicht freigegeben werden, was zu Systemlecks führt. Wenn wir den Thread trennen, werden die Thread-Ressourcen automatisch freigegeben, wenn der Thread beendet wird. Das Linux-Betriebssystem bietet pthread_detachFunktionen zum Trennen von Threads.

//pthread_detach函数所在的头文件和函数声明
#include <pthread.h>

int pthread_detach(pthread_t thread);
  • Thread-Parameter: die Thread-ID, die getrennt werden soll.
  • Nach der Trennung des Threads können keine Vorgänge mehr ausgeführt werden pthread_join. Bei Verwendung wird ein Fehler gemeldet.

Schreiben Sie zunächst den folgenden Code zum Testen:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstring>

using namespace std;

void *thread_run(void *args)
{
    
    
    int cnt = 5;
    while(true)
    {
    
    
        cout << (char*)args << " : " << cnt-- << endl;
        if (cnt==0) break;
    }
    return nullptr;
}

int main()
{
    
    
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, (void*)"new_thread");
    pthread_detach(t);//分离线程

    int n = pthread_join(t, nullptr);//线程等待
    if (n != 0)
    {
    
    
        cerr << "pthread_join error: " << n << " : " << strerror(n) << endl; 
    }
    return 0;
}

Kompilieren Sie den Code und führen Sie ihn aus, um die Ergebnisse anzuzeigen:

Bild-20230923193701650

Aufgrund des Planungsproblems des Hauptthreads und des neuen Threads werden die beiden oben genannten Situationen verursacht. In beiden Fällen wird jedoch nach der Trennung des neuen Threads während des Wartevorgangs ein Fehler gemeldet.

Schreiben Sie dann den folgenden Code zum Testen:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstring>

using namespace std;

void *thread_run(void *args)
{
    
    
    pthread_detach(pthread_self());//线程分离
    int cnt = 5;
    while(true)
    {
    
    
        cout << (char*)args << " : " << cnt-- << endl;
        sleep(1);
        if (cnt==0) break;
    }
    return nullptr;
}

int main()
{
    
    
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, (void*)"new_thread");
    int n = pthread_join(t, nullptr);//线程等待
    if (n != 0)
    {
    
    
        cerr << "pthread_join error: " << n << " : " << strerror(n) << endl; 
    }
    return 0;
}

Kompilieren Sie den Code und führen Sie ihn aus, um die Ergebnisse anzuzeigen:

pthreadTest6

Wenn der Hauptthread zuerst wartet und dann den neuen Thread trennt, führt dies zu der oben genannten Situation: Auch wenn der Thread getrennt wird und der Wartevorgang keinen Fehler meldet. Denn wenn der Hauptthread einen Wartevorgang ausführt, wird festgestellt, dass sich der neue Thread nicht getrennt hat, und er wechselt direkt in den Blockierungsstatus und wartet auf den neuen Thread, sodass kein Fehler gemeldet wird.

Verstehen Sie Thread-Bibliotheken und Thread-IDs

Unter dem Linux-Betriebssystem gibt es keine echten Threads, sondern Threads, die durch Lightweight-Prozesse simuliert werden. Daher kann das Linux-Betriebssystem nur in Form von Lightweight-Prozessen und nicht in Threads verwaltet werden, und die Thread-Bibliothek muss Benutzer bereitstellen verschiedene Thread- Verwandte Vorgänge, und die Thread-Bibliothek muss einige Thread-Verwaltungsvorgänge ausführen, über die das Betriebssystem nicht verfügt. Wenn daher die Thread-Bibliothek zum Erstellen eines Threads verwendet wird, muss die Thread-Bibliothek eine entsprechende Datenstruktur erstellen, um die Attribute des Threads aufzuzeichnen Es wird zum Verwalten von Threads verwendet. Wenn die Thread-Bibliothek anschließend aufgerufen wird, um einen Thread zu betreiben, verwendet die Thread-Bibliothek die zuvor erstellte Datensatzattributstruktur und einige gekapselte Systemschnittstellen, um Thread-Operationen zu implementieren.

Bild-20230923203756664

Wenn die Thread-Bibliothek die Thread-Verwaltungsstruktur organisiert, zeichnet sie diese linear im Prozessadressraum auf, und die erste Adresse der Thread-Verwaltungsstruktur im Prozessadressraum ist die von der Thread-Bibliothek bereitgestellte Thread-ID.

Bild-20230923204156954

In der von der Thread-Bibliothek bereitgestellten Thread-Verwaltungsstruktur gibt es einen Teil des Speicherplatzes, der als Thread-Stapel bezeichnet wird. Der Thread-Stapel ist ein privater Stapel für jeden neuen Thread. Der neue Thread speichert die erstellten temporären Variablen im Thread-Stapel und trennt sie Daten jedes Threads. Für die Datenverwaltung verwendet der Hauptthread die Stapelstruktur im Prozessadressraum.

Hinweis: Unter dem Linux-Betriebssystem sind die von C++ bereitgestellten Thread-Operationen eine Kapselung der nativen Thread-Bibliothek.

Schreiben Sie zum Testen den folgenden Code:

#include <iostream>
#include <unistd.h>
#include <thread>

using namespace std;

void run1()
{
    
    
    while(true)
    {
    
    
        cout << "thread 1" << endl;
        sleep(1);
    }
}
void run2()
{
    
    
    while(true)
    {
    
    
        cout << "thread 2" << endl;
        sleep(1);
    }
}
void run3()
{
    
    
    while(true)
    {
    
    
        cout << "thread 3" << endl;
        sleep(1);
    }
}


int main()
{
    
    
    thread th1(run1);
    thread th2(run2);
    thread th3(run3);

    th1.join();
    th2.join();
    th3.join();

    return 0;
}

Beim Kompilieren ohne -lpthreadOptionen und Ausführen des Programms sind die Ergebnisse wie folgt:

Bild-20230923205821115

Da die Thread-Operationen von C++ aus der Kapselung der nativen Thread-Bibliothek abgeleitet werden, kann das Betriebssystem die dynamische Bibliothek bei der Ausführung des Programms nicht korrekt in den Speicher laden, wenn die native Thread-Bibliothek während der Kompilierung nicht verknüpft wird, was dazu führt, dass das Programm ausgeführt wird kann nicht normal ausgeführt werden.

Ergänzung: Der lokale Thread-Speicher in der Thread-Verwaltungsstruktur wird zum Speichern Thread-bezogener globaler Variablen und Thread-Kontextinformationen sowie zum Isolieren vertraulicher Daten verwendet.

Bild-20230927102412819

Schreiben Sie zum Testen den folgenden Code:

#include <iostream>
#include <unistd.h>
#include <pthread.h>

using namespace std;

__thread int g_val = 100;//__thread将数据以线程全局变量的形式创建

void *thread_run(void *args)
{
    
    
    char* tname = static_cast<char*>(args);
    int cnt = 5;
    while (true)
    {
    
    
        cout << tname << ":" << (u_int64_t)cnt << ",g_val: " << g_val << ",&g_val: " << &g_val << endl;
        if ((cnt--)==0)
            break;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    
    
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_create(&tid1, nullptr, thread_run, (void*)"thread1");
    pthread_create(&tid2, nullptr, thread_run, (void*)"thread2");
    pthread_create(&tid3, nullptr, thread_run, (void*)"thread3");
    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    pthread_join(tid3, nullptr);
    return 0;
}

Kompilieren Sie den Code und führen Sie ihn aus, um die Ergebnisse anzuzeigen:

Bild-20230927102959242

Ich denke du magst

Origin blog.csdn.net/csdn_myhome/article/details/133343059
Empfohlen
Rangfolge