[Linux]マルチスレッドプログラミング

[Linux]マルチスレッドプログラミング

Linux オペレーティング システムでは実際のスレッドはなく、プロセス内の軽量プロセス (LWP) によってシミュレートされたスレッドが存在するため、Linux オペレーティング システムはプロセス操作用のシステム インターフェイスのみを提供します。ただし、ユーザー操作の利便性を考慮して、Linux オペレーティング システムはユーザーレベルのネイティブ スレッド ライブラリを提供します。ネイティブ スレッド ライブラリはシステム インターフェイスをカプセル化し、ユーザーが実際のスレッドであるかのようにスレッド操作を実行できるようにします。スレッド ライブラリを使用する場合、コードをコンパイルするときに、リンクするスレッド ライブラリを指定する必要があります。

pthread_create関数

pthread_create関数はスレッドを作成するために使用されます。

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

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • **スレッドパラメータ:** スレッドIDを返す

  • **attr パラメータ: **スレッドの属性を設定します。デフォルトの属性を使用するには、Attr は NULL です。

  • start_routine パラメータ: スレッド開始後に実行される関数のアドレスです。

  • **arg パラメータ:**スレッド起動関数に渡されるパラメータ

  • **戻り値:** 成功した場合は 0 を返し、失敗した場合はエラー コードを返します。(スレッドは同じアドレス空間を共有するため、グローバル変数 errno を設定してもエラー コードは記録されません)

テスト用に次のコードを作成します。

#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;
}

コードをコンパイルして実行し、結果を確認します。

pthreadTest1

さらに、Linux オペレーティング システムの命令を使用してps -aL | head -1 && ps -aL | grep 进程名スレッドを表示できます。

pthreadTest2

LWPid と pid が同じものがメインスレッドで、残りは新しいスレッドです。

pthread_join関数

プロセスと同様に、Linux オペレーティング システムでスレッドが終了した後も、新しいスレッドがリサイクルされるまで待機する必要があるため、関数が提供されていますpthread_join

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

int pthread_join(pthread_t thread, void **retval);
  • スレッドパラメータ:待機してリサイクルする新しいスレッド ID。(pthread_create関数が新しいスレッドを作成するときの出力パラメータ thread)
  • retval パラメータ:スレッド終了の戻り値を出力パラメータとして受け取ります。
  • **戻り値:** 成功した場合は 0 を返し、失敗した場合はエラー コードを返します。
  • スレッドがキャンセルされた場合は、retval パラメータが受信されますPTHREAD_CANCELED ((void *) -1)

注:スレッド例外はプロセスを直接終了させるシグナルを生成するため、スレッドがリサイクルを待機しているときに例外検出を考慮する必要はありません。

テスト用に次のコードを作成します。

#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;
}

コードをコンパイルして実行すると、結果が表示されます。実行時にプロセスを検出するには、次の手順を使用しますwhile :; do ps -aL | head -1 && ps -aL | grep 进程名; sleep 1; done

pthreadTest3

pthread_joinスレッドが終了して戻り値 0 のデータが返され、メイン スレッド呼び出しが戻り値を正常に受け取ることができることがわかります。

pthread_exit関数

Linux オペレーティング システムでスレッドが終了する方法:

  1. スレッド実行関数が終了し、return が返されます。
  2. 関数を呼び出してphread_exitスレッドを終了する

注:スレッドが関数を実行するか、関数を返すか、phread_exit関数を呼び出してスレッドを終了するかに関係なく、void *最終的にはあるタイプの戻り値がメインスレッドに返されます。

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

void pthread_exit(void *retval);
  • retval パラメータ:スレッド終了の戻り値としてメインスレッドに返されます。

テスト用に次のコードを作成します。

#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;
}

コードをコンパイルして実行し、結果を確認します。

pthreadTest4

スレッドが終了してphread_exit値 1 のデータを返し、メイン スレッド呼び出しがpthread_join戻り値を正常に受け取ることができることがわかります。

pthread_cancel関数

pthread_cancelこの関数は実行中のスレッドをキャンセルできます。

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

int pthread_cancel(pthread_t thread);
  • thread パラメータ:キャンセルされるスレッド ID。
  • **戻り値:** 成功した場合は 0 を返し、失敗した場合はエラー コードを返します。

テスト用に次のコードを作成します。

#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;
}

コードをコンパイルして実行し、結果を確認します。

pthreadTest5

無限ループ内の新しいスレッドがメイン スレッドによってキャンセルされました。新しいスレッドがキャンセルされた後、メイン スレッドpthread_join関数は を受け取りましたPTHREAD_CANCELED ((void *) -1)

pthread_self 関数

pthread_selfこの関数は、現在のスレッドのスレッド ID を取得するために使用されます。

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

pthread_t pthread_self(void);
  • 戻り値:呼び出しスレッドのスレッド ID を返します。

テスト用に次のコードを作成します。

#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;
}

コードをコンパイルして実行し、結果を確認します。

画像-20230923174750463

pthread_detach 関数

デフォルトでは、新しく作成されたスレッドが終了した後、そのスレッドを操作する必要がありますpthread_join。そうしないとリソースを解放できず、システム リークが発生します。スレッドを分離すると、スレッドの終了時にスレッド リソースが自動的に解放されます。Linux オペレーティング システムには、pthread_detachスレッドを分離するための機能が用意されています。

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

int pthread_detach(pthread_t thread);
  • thread パラメータ:分離するスレッド ID。
  • スレッドが分離された後は操作を実行できずpthread_join、使用するとエラーが報告されます。

まず、テスト用に次のコードを作成します。

#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;
}

コードをコンパイルして実行し、結果を確認します。

画像-20230923193701650

メインスレッドと新規スレッドのスケジューリングの問題により、上記の 2 つの状況が発生しますが、いずれの場合も、新規スレッドが分離された後、待機中にエラーが報告されます。

次に、テスト用に次のコードを作成します。

#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;
}

コードをコンパイルして実行し、結果を確認します。

pthreadTest6

メインスレッドが最初に待機してから新しいスレッドを分離すると、スレッドが分離されても待機中の操作でエラーが報告されないという上記の状況が発生します。メインスレッドが待機操作を実行しているときに、新しいスレッドが分離されていないことが検出され、直接ブロックして新しいスレッドを待機する状態になるため、エラーは報告されません。

スレッド ライブラリとスレッド ID を理解する

Linux オペレーティング システムでは、実際のスレッドはなく、軽量プロセスによってシミュレートされたスレッドが存在します。したがって、Linux オペレーティング システムは、スレッドではなく軽量プロセスの形式でのみ管理でき、スレッド ライブラリは、ユーザーがさまざまなスレッドを提供する必要があります。関連する操作、およびスレッド ライブラリは、オペレーティング システムにはないいくつかのスレッド管理操作を実行する必要があるため、スレッド ライブラリを使用してスレッドを作成する場合、スレッド ライブラリは、スレッドの属性を記録するために対応するデータ構造を作成する必要があります。スレッドを管理するために使用されます。その後、スレッド ライブラリがスレッドを操作するために呼び出されるとき、スレッド ライブラリは、以前に作成されたレコード属性構造といくつかのカプセル化されたシステム インターフェイスを使用して、スレッド操作を実装します。

画像-20230923203756664

スレッド ライブラリがスレッド管理構造を編成すると、それをプロセス アドレス空間に線形に記録します。プロセス アドレス空間内のスレッド管理構造の最初のアドレスは、スレッド ライブラリによって提供されるスレッド ID です。

画像-20230923204156954

スレッドライブラリが提供するスレッド管理構造には、スレッドスタックと呼ばれる空間の一部があり、スレッドスタックは新規スレッドごとのプライベートスタックであり、新規スレッドは作成した一時変数をスレッドスタックに格納し、スレッドスタックに分割します。各スレッドのデータをデータ管理に使用し、メインスレッドはプロセスアドレス空間のスタック構造を使用します。

注: Linux オペレーティング システムでは、C++ によって提供されるスレッド操作はネイティブ スレッド ライブラリのカプセル化です。

テスト用に次のコードを作成します。

#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;
}

オプションを指定せずにコンパイルし-lpthreadてプログラムを実行すると、結果は次のようになります。

画像-20230923205821115

C++ のスレッド操作はネイティブ スレッド ライブラリのカプセル化から派生するため、コンパイル中にネイティブ スレッド ライブラリがリンクされていない場合、プログラムの実行時にオペレーティング システムがダイナミック ライブラリをメモリに正しくロードできず、プログラムが正常に実行できません。

補足:スレッド管理構造内のスレッド ローカル ストレージは、スレッド関連のグローバル変数、スレッド コンテキスト情報を格納し、機密データを分離するために使用されます。

画像-20230927102412819

テスト用に次のコードを作成します。

#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;
}

コードをコンパイルして実行し、結果を確認します。

画像-20230927102959242

おすすめ

転載: blog.csdn.net/csdn_myhome/article/details/133343059