C++スレッド学習

17552 ワード

スレッドの概念
C++のスレッドのText SegmentとData Segmentは共有されており、関数を定義すれば各スレッドで呼び出すことができ、グローバル変数を定義すれば各スレッドでアクセスできる.このほか、スレッドは次のプロセスリソースと環境を共有します.
  • ファイル記述子
  • 信号毎の処理方式
  • 現在の作業ディレクトリ
  • ユーザidおよびグループid
  • ただし、一部のリソースはスレッドごとに1つずつあります.
  • スレッドid
  • コンテキストは、様々なレジスタの値、プログラムカウンタ、およびスタックポインタ
  • を含む.
  • スタック空間
  • errno変数
  • 信号シールド
  • スケジューリング優先度
  • 学習するスレッドライブラリ関数は、POSIX threadまたはpthreadと呼ばれるPOSIX規格によって定義されます.
    スレッド制御
    スレッドの作成
    スレッドを作成する関数のプロトタイプは次のとおりです.
    #include <pthread.h>
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

    戻り値:0が正常に返され、エラー番号が返されませんでした.
    スレッドでpthread_を呼び出すcreate()新しいスレッドを作成すると、現在のスレッドはpthread_からcreate()は下へ実行を続け、新しいスレッドが実行するコードはpthread_に渡されます.createの関数ポインタstart_义齿start_routine関数はpthread_を介してパラメータを受信します.createのargパラメータは、呼び出し者が自分で定義したパラメータタイプvoid*に渡されます.start_routineの戻り値タイプもvoid*であり,このポインタの意味は呼び出し者自身が定義する.start_routineが戻ると、このスレッドは終了し、他のスレッドはpthread_を呼び出すことができます.joinはstart_を得るroutineの戻り値.
    pthread_createが正常に返されると、新しく作成したスレッドのidがthreadパラメータが指すメモリユニットに記入されます.プロセスidのタイプはpid_であることがわかりますt,各プロセスのidはシステム全体で一意であり,getpidを呼び出すと現在のプロセスのidが得られ,正の整数値である.スレッドidのタイプはthread_tは、現在のプロセスでのみ一意であることを保証し、異なるシステムでthread_tこのタイプは異なる実装があり、整数値であるか、構造体であるか、アドレスであるかのいずれかであるため、単純に整数としてprintfで印刷し、pthread_を呼び出すことはできない.selfは、現在のスレッドのidを取得することができる.
    まず簡単な例を書きます.
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <unistd.h>
    
    pthread_t ntid;
    
    void printids(const void *t)
    {
            char *s = (char *)t;
        pid_t      pid;
        pthread_t  tid;
    
        pid = getpid();
        tid = pthread_self();
        printf("%s pid %u tid %u (0x%x)
    "
    , s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid); } void *thr_fn(void *arg) { printids(arg); return NULL; } int main(void) { int err; err = pthread_create(&ntid, NULL, thr_fn, (void *)"Child Process:"); if (err != 0) { fprintf(stderr, "can't create thread: %s
    "
    , strerror(err)); exit(1); } printids("main thread:"); sleep(1); return 0; }

    コンパイルの実行結果は次のとおりです.
    g++ thread.cpp -o thread -lpthread
    ./thread
    main thread: pid 21046 tid 3612727104 (0xd755d740)
    Child Process: pid 21046 tid 3604444928 (0xd6d77700)

    結果から分かるようにthread_tタイプはアドレス値であり、同じプロセスに属する複数のスレッド呼び出しgetpidは同じプロセス番号を得ることができ、pthread_を呼び出すことができる.selfで得られたスレッド番号はそれぞれ異なる.
    いずれかのスレッドがexitまたは_を呼び出した場合exitでは、プロセス全体のすべてのスレッドが終了します.main関数returnからexitを呼び出すことにも相当します.新しく作成したスレッドが実行されずに終了することを防止するために、main関数returnの前に1秒遅延します.これは、メインスレッドが1秒待っていても、カーネルが新しく作成したスレッドの実行をスケジュールするとは限りません.次に、私たちは比較的良い解決方法を勉強します.
    スレッドの終了
    プロセス全体を終了するのではなく、スレッドのみを終了する必要がある場合は、次の3つの方法があります.
  • スレッド関数returnから.この方法はメインスレッドに適応せず,main関数returnからexitを呼び出すことに相当する.
  • スレッドはpthread_を呼び出すことができますcancelは、同じプロセス内の別のスレッドを終了します.
  • スレッドはpthread_を呼び出すことができるexitは自分を終了します.

  • ここでは主にpthread_について説明しますexitとpthread_joinの使い方.
    #include <pthread.h>
    
    void pthread_exit(void *value_ptr);

    value_ptrはvoid*タイプであり、スレッド関数の戻り値と同様に、他のスレッドはpthread_を呼び出すことができます.joinはこのポインタを取得します.注意、pthread_exitまたはreturnが返すポインタが指すメモリユニットは、グローバルまたはmallocで割り当てられている必要があります.他のスレッドがこの戻りポインタを得たときにスレッド関数が終了したため、スレッド関数のスタックに割り当てることはできません.
    #include <pthread.h>
    
    int pthread_join(pthread_t thread, void **value_ptr);

    戻り値:0が正常に返され、エラー番号が返されませんでした.
    関数を呼び出すスレッドは、idがthreadのスレッドが終了するまで待機します.threadスレッドはpthread_を介して異なる方法で終了するjoinで得られた終了状態は異なり、以下にまとめる.
  • threadスレッドがreturnで返された場合value_ptrが指すセルにはthreadスレッド関数の戻り値が格納されている.
  • threadスレッドが別のスレッドによって呼び出された場合pthread_キャンセル異常終了、value_ptrが指すセルは定数PTHREAD_を格納するCANCELED.
  • threadスレッドが自分でpthreadを呼び出した場合exit終了、value_ptrが指すセルはpthread_に渡されるexitのパラメータ.

  • threadスレッドの終了状態に興味がない場合は、NULLをvalue_に渡すことができます.ptrパラメータ.参照コードは次のとおりです.
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <unistd.h>
    
    void* thread_function_1(void *arg)
    {
        printf("thread 1 running
    "
    ); return (void *)1; } void* thread_function_2(void *arg) { printf("thread 2 exiting
    "
    ); pthread_exit((void *) 2); } void* thread_function_3(void* arg) { while (1) { printf("thread 3 writeing
    "
    ); sleep(1); } } int main(void) { pthread_t tid; void *tret; pthread_create(&tid, NULL, thread_function_1, NULL); pthread_join(tid, &tret); printf("thread 1 exit code %d
    "
    , *((int*) (&tret))); pthread_create(&tid, NULL, thread_function_2, NULL); pthread_join(tid, &tret); printf("thread 2 exit code %d
    "
    , *((int*) (&tret))); pthread_create(&tid, NULL, thread_function_3, NULL); sleep(3); pthread_cancel(tid); pthread_join(tid, &tret); printf("thread 3 exit code %d
    "
    , *((int*) (&tret))); return 0; }

    実行結果:
    thread 1 running
    thread 1 exit code 1
    thread 2 exiting
    thread 2 exit code 2
    thread 3 writeing
    thread 3 writeing
    thread 3 writeing
    thread 3 exit code -1

    Linuxのpthreadライブラリにおける定数PTHREAD_CANCELEEDの値は-1です.ヘッダファイルでhで定義を見つけます.
    #define PTHREAD_CANCELED ((void *) -1)

    スレッド間同期
    複数のスレッドが共有データに同時にアクセスすると、競合する可能性があります.たとえば、2つのスレッドはグローバル変数を1増加させなければなりません.この操作は、あるプラットフォームで3つの命令が必要です.
  • メモリから変数値をレジスタに読み出す.
  • レジスタ値に1を加算.
  • レジスタの値をメモリに書き込みます.

  • この場合,2つのプロセスがレジスタ変数値を同時に操作する場合が起こりやすく,最終結果が正しくない.
    解決策は、反発ロック(Mutex,Mutual Exclusive Lock)を導入することであり、ロックを取得したスレッドは「読み取り-修正-書き込み」の操作を完了し、他のスレッドにロックを解除することができ、ロックを取得していないスレッドは待つしかなく共有データにアクセスできない.このように、「読み取り-修正-書き込み」の3ステップの操作は原子操作を構成し、すべて実行しないか、実行しないか、この操作は、途中で中断されたり、他のプロセッサで並列に行われたりしません.
    Mutex用pthread_mutex_tタイプの変数は、このように初期化および破棄できることを示す.
    #include <pthread.h>
    
    int pthread_mutex_destory(pthread_mutex_t *mutex);
    int pthread_mutex_int(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
    pthread_mutex_t mutex = PTHEAD_MUTEX_INITIALIZER;

    戻り値:0が正常に返され、エラー番号が返されませんでした.
    pthread_でmutex_Init関数初期化のMutexはpthread_mutex_destroy破棄.Mutex変数が静的に割り当てられている場合(グローバル変数またはstatic変数)、マクロでPTHREAD_を定義することもできます.MUTEX_INITIALIZERで初期化するのは、pthread_mutex_Initは初期化され、attrパラメータはNULLです.Mutexのロックおよびロック解除操作は、次の関数を使用できます.
    #include <pthread.h>
    
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    int pthread_mutex_unlock(pthread_mutex_t *mutex);

    戻り値:0が正常に返され、エラー番号が返されませんでした.
    スレッドはpthread_を呼び出すことができますmutex_ロックはMutexを取得し、別のスレッドがpthread_を呼び出した場合mutex_ロックがMutexを取得すると、別のスレッドがpthread_を呼び出すまで、現在のスレッドが待機する必要があります.mutex_unlockはMutexを解放し、現在のスレッドが起動してから、Mutexを取得して実行を続行します.
    Mutexを用いて,上記の2つのスレッドが同時にグローバル変数+1に対して乱れをもたらす可能性のある問題を解決した.
    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    #define NLOOP 5000
    
    int counter;
    pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
    
    void *do_add_process(void *vptr)
    {
        int i, val;
    
        for (i = 0; i < NLOOP; i ++) {
            pthread_mutex_lock(&counter_mutex);
            val = counter;
            printf("%x:%d
    "
    , (unsigned int)pthread_self(), val + 1); counter = val + 1; pthread_mutex_unlock(&counter_mutex); } return NULL; } int main() { pthread_t tida, tidb; pthread_create(&tida, NULL, do_add_process, NULL); pthread_create(&tidb, NULL, do_add_process, NULL); pthread_join(tida, NULL); pthread_join(tidb, NULL); return 0; }

    これにより、実行するたびに10000に表示されます.ロックメカニズムを外すと、問題が発生する可能性があります.このメカニズムはJavaのsynchronizedブロックメカニズムに似ている.
    Condition Variable
    スレッド間の同期には、スレッドAがある条件が成立してから実行を継続する必要がある場合もあります.現在、この条件が成立しないと、スレッドAは待機をブロックし、スレッドBは実行中にこの条件を成立させ、スレッドAを起動して実行を継続します.pthreadライブラリで条件変数(Conditiion Variable)によって1つの条件を待つか、この条件を待つスレッドを起動します.Condition Variable用pthread_cond_tタイプの変数は、このように初期化および破棄できることを示す.
    #include <pthread.h>
    
    int pthread_cond_destory(pthread_cond_t *cond);
    int pthread_cond_init(pthead_cond_t *cond, const pthread_condattr_t *attr);
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

    戻り値:0が正常に返され、エラー番号が返されませんでした.
    Mutexの初期化と破棄と同様、pthread_cond_init関数はCondition Variableを初期化し、attrパラメータはNULLでデフォルト属性を表し、pthread_cond_destroy関数は、Condition Variableを破棄します.Condition Variableが静的に割り当てられている場合は、マクロでPTHEAD_を定義することもできます.COND_INITIALIZER初期化は、pthread_cond_Init関数は初期化され、attrパラメータはNULLです.Condition Variableの操作は以下の関数を使用できます.
    #include <pthread.h>
    
    int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    int pthread_cond_broadcast(pthread_cond_t *cond);
    int pthread_cond_signal(pthread_cond_t *cond);

    Condition Variableは常に1つのMutexと組み合わせて使用されていることがわかります.スレッドはpthread_を呼び出すことができますcond_waitはCondition Variableで待機をブロックし、この関数は次の3つのステップで動作します.
  • Mutexを解放します.
  • ブロック待ち.
  • が起動されると、Mutexが再取得され、戻ってくる.

  • pthread_cond_timedwait関数には、待機タイムアウトを設定する追加のパラメータがあり、abstimeで指定された時刻になっても現在のスレッドを起動する他のスレッドがない場合は、ETIMEDOUTに戻ります.スレッドはpthread_を呼び出すことができますcond_Signalは、あるCondition Variableで待機している別のスレッドを起動し、pthread_を呼び出すこともできます.cond_broadcastは、このCondition Variableで待機しているすべてのスレッドを起動します.
    次のプログラムは、生産者−消費者の例を示し、生産者はチェーンテーブルのヘッダーに構造体を直列に生産し、消費者はヘッダーから構造体を取り出す.
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <unistd.h>
    
    struct msg {
        struct msg *next;
        int num;
    };
    
    struct msg *head;
    pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    
    void* consumer(void *p)
    {
        struct msg *mp;
    
        for(;;) {
            pthread_mutex_lock(&lock);
            while (head == NULL) {
                pthread_cond_wait(&has_product, &lock);
            }
            mp = head;
            head = mp->next;
            pthread_mutex_unlock(&lock);
            printf("Consume %d
    "
    , mp->num); free(mp); sleep(rand() % 5); } } void* producer(void *p) { struct msg *mp; for(;;) { mp = (struct msg *)malloc(sizeof(*mp)); pthread_mutex_lock(&lock); mp->next = head; mp->num = rand() % 1000; head = mp; printf("Product %d
    "
    , mp->num); pthread_mutex_unlock(&lock); pthread_cond_signal(&has_product); sleep(rand() % 5); } } int main() { pthread_t pid, cid; srand(time(NULL)); pthread_create(&pid, NULL, producer, NULL); pthread_create(&cid, NULL, consumer, NULL); pthread_join(pid, NULL); pthread_join(cid, NULL); return 0; }