muduoライブラリ学習02スレッドに適用可能な機会と一般的に使用されるプログラミングモデル

元のリンク:https//blog.csdn.net/zhoucheng05_13/article/details/97612135

1スレッドの作成

スレッドを作成し、プロセスIDとスレッドIDをメインスレッドとサブスレッドに出力します。

サンプルコード

#include "apue.h"
#include<pthread.h>

pthread_t tdno;

//打印进程id、线程id
void printids(const char* s){
    
    
    pid_t pid;
    pthread_t tid;

    pid = getpid();
    tid = pthread_self();
    printf("%s pid:%lu tid:%lu (0x%lx)\n",s,(unsigned long)pid,(unsigned long)tid,(unsigned long)tid);
}


void *thr_fn(void *arg){
    
    
    printids("new thread: ");
    return ((void*)0);
}


int main(void){
    
    
    int err;

    //之所以不直接使用printids,是由于pthread_create对参数的要求
    err = pthread_create(&tdno,NULL,thr_fn,NULL);
    if(err != 0)
        exit(-1);
    printids("main thread: ");
    sleep(1);
    exit(0);
}
123456789101112131415161718192021222324252627282930313233

試験結果

[ ~/unix_practice]$ ./threadCreate 
main thread:  pid:13548 tid:139868882683680 (0x7f35c3114720)
new thread:  pid:13548 tid:139868865541888 (0x7f35c20bb700)
[ ~/unix_practice]$ ./threadCreate 
main thread:  pid:13551 tid:139870974031648 (0x7f363fb8b720)
new thread:  pid:13551 tid:139870956889856 (0x7f363eb32700)
[ ~/unix_practice]$ ./threadCreate 
new thread:  pid:13553 tid:140530159355648 (0x7fcfba3a8700)
main thread:  pid:13553 tid:140530176497440 (0x7fcfbb401720)
123456789

実行結果から、メインスレッドと新しいスレッドの実行順序はランダムであることがわかります。これらは同じプロセスIDと異なるスレッドIDを持っていますが、同じメモリ領域にあります。

2スレッド終端

2.1プロセスの終了

プロセス内のいずれかのスレッドがexit、_Exit、または_exitを呼び出すと、プロセスは終了します。

2.2スレッドの終了のみ

プロセスを終了せずに、スレッドを終了する方法は3つあります。

  1. 戻ります。スレッドは実行を終了し、起動ルーチンから戻ります。
  2. キャンセルされました。pthread_cancel関数を呼び出す同じプロセス内の他のスレッドによってキャンセルされました。
  3. pthread_exit関数が呼び出されます。スレッド内でpthread_exit関数を呼び出して、スレッドを終了します。

2.3スレッドの終了ステータスを取得する

スレッドの終了ステータスは、pthread_join関数を介して取得できます。

int pthread_join(pthread_t thread, void **rval_ptr);
1

スレッドが正常に終了すると、rval_ptrに戻りコードが含まれます。スレッドがキャンセルされると、rval_ptrが指すメモリユニットはPTHREAD_CANCELEDに設定されます。

pthread_joinは、待機中のスレッドが終了するまで現在のスレッドをブロックすることに注意してください。

2.4スレッドのキャンセル

同じプロセス内のスレッドは、pthread_cancel関数を呼び出して別のスレッドをキャンセルできます。その効果は、PTHREAD_CANCELEDパラメーターを指定してpthread_exit関数を使用するのと似ています。pthread_cancelはスレッドが終了するのを待たず、要求を行うだけで、スレッドはクリーンアップルーチンを落ち着いて呼び出すことができます。

2.5スレッドクリーナー

スレッドは複数のクリーンアップ手順を作成でき、クリーンアップ手順はスタックに記録されるため、それらの実行順序は登録の順序と逆になります。pthread_cleanup_pushはクリーンアッププログラムを登録するために使用され、pthread_cleanup_popはクリーンアッププログラムを削除するために使用されます。これらの2つの関数はペアで使用する必要があります(マクロとして実装できるため、左右の中括弧をペアにする必要があります)。

スレッドクリーナーは、次の3つの状況で呼び出されます。

  1. pthread_exitが呼び出されたとき。
  2. キャンセルリクエストに対応する場合。
  3. ゼロ以外の実行パラメーターを使用してpthread_cleanup_popを呼び出す場合。

スレッドが起動ルーチンから戻って終了した場合、そのクリーンアップルーチンは呼び出されないことに注意してください。

2.6スレッドデタッチpthread_detach

スレッドを作成するデフォルトの状態は結合可能です。スレッドの実行が終了しても結合されていない場合、その状態はゾンビプロセスに似ています。つまり、一部のリソースはリサイクルされていません(終了ステータスコード)。したがって、作成スレッドはpthread_joinを呼び出して、子スレッドリソースを再利用する必要があります(waitpidと同様)。

ただし、pthread_joinを呼び出した後に新しいスレッドが終了しない場合、呼び出し元のスレッドはブロックされます。ネットワークサービスなど、これが不要な場合もあります。この場合、子スレッドをデタッチ状態(デタッチ)に設定できます。この状態では、スレッドの実行後にすべてのリソースが自動的に解放されます。切り離された状態を設定するには、次の2つの方法があります。

  1. 子スレッドはpthread_detach(pthread_self())を呼び出します。
  2. 親スレッドはpthread_detach(thread_id)を呼び出します。これは非ブロッキングであり、すぐに戻ります。

3スレッドの同期

3.1スレッド同期方法の要約

  1. Mutex:ロック/ロック解除の状態は2つだけで、一度に1つのスレッドのみがロックされます。
  2. 読み取り/書き込みロック:読み取りロック、書き込みロック、およびロックなしの3つの状態があります。読み取りロックは、複数のスレッドによって占有される可能性があります。
  3. 条件変数:ミューテックスによって保護された変数、自己計算条件、より柔軟。
  4. スピンロック:スピン待機、スケジューリングを排除します。
  5. バリア:マルチスレッドの並列同期。

3.2ミューテックス

ミューテックスはpthread_mutex_tデータ型で表されます。これは基本的にロックであり、一度に1つのスレッドのみがアクセスできます。

3.2.1ミューテックス操作

Mutexには主に次の5つの操作があります。

1pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t *attr);
// 初始化锁变量mutex。
// attr为锁属性,NULL值为默认属性。

2pthread_mutex_lock(pthread_mutex_t *mutex);
// 加锁(阻塞操作)

3pthread_mutex_trylock(pthread_mutex_t *mutex);
// 试图加锁(不阻塞操作)
// 当互斥锁空闲时将占有该锁;否则立即返回
// 但是与2不一样的是当锁已经在使用的时候,返回为EBUSY,而不是挂起等待。

4pthread_mutex_unlock(pthread_mutex_t *mutex);
释放锁

5pthread_mutex_destroy(pthread_mutex_t *mutex);
使用完后删除
1234567891011121314151617

3.2.2ミューテックスの使用例

2つのスレッドを使用して0〜4を個別に出力し、スレッドの同期に相互排除を使用します。

#include <unistd.h>
  2 #include <pthread.h>
  3 #include <stdio.h>
  4 
  5 pthread_mutex_t mutex;
  6 void *print_msg(void *arg){
    
    
  7         int i=0;
  8         pthread_mutex_lock(&mutex);
  9         for(i=0;i<5;i++){
    
    
 10                 printf("output : %d\n",i);
 11                 usleep(100);
 12         }
 13         pthread_mutex_unlock(&mutex);
 14 }
 15 int main(int argc,char** argv){
    
    
 16         pthread_t id1;
 17         pthread_t id2;
 18         pthread_mutex_init(&mutex,NULL);
 19         pthread_create(&id1,NULL,print_msg,NULL);
 20         pthread_create(&id2,NULL,print_msg,NULL);
 21         pthread_join(id1,NULL);
 22         pthread_join(id2,NULL);
 23         pthread_mutex_destroy(&mutex);
 24         return 1;
 25 }
12345678910111213141516171819202122232425

試験結果

[~/unix_practice]$ g++ -l pthread -o mutexOpt mutexOpt.cpp 
[ ~/unix_practice]$ ./mutexOpt 
output : 0
output : 1
output : 2
output : 3
output : 4
output : 0
output : 1
output : 2
output : 3
output : 4
123456789101112

3.2.3デッドロックを回避する

ミューテックスの使用には特別な注意が必要です。そうしないと、デッドロックが発生しやすくなります。たとえば、スレッドが同じミューテックスを2回ロックしようとすると、デッドロック状態になります。プログラムが複数のミューテックスを使用すると、2つのスレッドが逆の順序でミューテックスを取得する可能性があります。デッドロックが発生します。

3.3読み取り/書き込みロック

読み取り/書き込みロックは、データ型pthread_rwlock_tで表されます。これには、読み取りロック状態、書き込みロック状態、およびロックなし状態の3つの状態があります。読み取りロックは同時に複数のスレッドで占有される可能性があり、読み取り/書き込みロックは、データ構造への読み取り数が書き込みよりもはるかに多い状況に非常に適しています。(通常、読み取り/書き込みロックが読み取りモードロック状態の場合、スレッドが書き込みモードでロックを取得しようとすると、読み取り/書き込みロックは通常、後続の読み取りモードロック要求をブロックします。これにより、読み取りモードロック、および保留中の書き込みモードロック要求が満たされていません。)

3.3.1読み取り/書き込みロック操作

#include<pthread.h>

//1. 读写锁初始化方法
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

//2. 读写锁销毁方法
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

//3. 读模式下加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

//4.写模式下加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

//5. 解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

//6. 尝试读锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

//7. 尝试写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

//8. 带超时的读锁
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);

//9. 带超时的写锁
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);


//所有函数,成功返回0;否则,返回错误编号
12345678910111213141516171819202122232425262728293031

3.3.2読み取り/書き込みロックの適用

読み取り/書き込みロックをジョブキューに適用して、キューの読み取りと書き込みを制御できます。

struct queue{
    
    
    struct job *q_head;
    struct job *q_tail;
    pthread_rwlock_t q_lock;
};
12345

3.4条件変数

条件変数は、複数のスレッドの待ち合わせ場所を提供するpthread_cond_dデータ型で表されます。条件自体はミューテックスによって保護されています。条件変数をミューテックスで使用すると、スレッドは特定の条件が非競合的に発生するのを待つことができます。

3.4.1条件変数の操作

条件変数に関連する操作は次のとおりです。

#include<pthread.h>

//1.条件变量初始化方法
//静态分配的条件变量可以通过赋值PTHREAD_COND_INITIALIZER来初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

//2.条件变量销毁方法
int pthread_cond_destroy(pthread_cond_t *cond);

//3.等待条件变量变为真。调用者把锁住的互斥量传给函数,函数自动把调用线程放到等待条件的线程列表上,并对互斥量解锁。该函数返回时,互斥量再次被锁住。
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

//4. 在给定时间内等待条件变量变为真
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr);

//5. 用于通知线程条件已满足。该函数至少能唤醒一个等待该条件的线程
int pthread_cond_signal(pthread_cond_t *cond);

//6.用于通知线程条件已满足。该函数将唤醒所有等待该条件的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
1234567891011121314151617181920

3.5スピンロック

スピンロックはタイプpthread_spinlock_tで表され、ロックが取得されるまでビジー待機(スピン)ブロッキング状態になります。スピンロックが適用できる状況は、ロックが短時間保持され、スレッドが再スケジュールにあまり多くのコストを費やしたくないということです。

スピンロックは、CPUリソースの浪費にもある程度つながります。スレッドがスピンしているとき、CPUは他に何もできません。多くのミューテックスは非常に効率的に実装されており、そのパフォーマンスは基本的にスピンロックを使用した場合と同じです。スピンロックは特定の状況でのみ役立ちます。

3.5.1スピンロック操作

//1. 初始化自旋锁
// pshared为PTHREAD_PROCESS_SHARED时,其他进程的线程也可以访问该锁
// pshared为PTHREAD_PROCESS_PRIVATE时,只有本进程中的线程才可以访问该锁
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

//2. 销毁自旋锁
int pthread_spin_destroy(pthread_spinlock_t *lock);

//3. 加锁。如果不能获取锁,则一直自旋
int pthread_spin_lock(pthread_spinlock_t *lock);

//4. 尝试加锁。如果不能获取锁,则立即返回EBUSY错误,不会自旋
int pthread_spin_trylock(pthread_spinlock_t *lock);

//5. 自旋锁解锁
int pthread_spin_unlock(pthread_spinlock_t *lock);
12345678910111213141516

3.6バリア

バリアは、ユーザーが複数のスレッドを調整して並列に動作するための同期メカニズムであり、タイプpthread_barrier_tで表されます。これにより、各スレッドは、すべての協調スレッドが特定のポイントに到達するまで待機し、そのポイントから実行を継続できます。pthread_join関数は、あるスレッドが別のスレッドが終了するまで待機できるようにする特別なバリアです。

3.6.1バリアの操作

//1. 屏障初始化。count表示继续运行前到达屏障的线程数目,attr用于配置屏障的属性。
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, nsigned int count);

//2. 销毁屏障
int pthread_barrier_destroy(pthread_barrier_t *barrier);

//3. 表明调用线程已经完成工作,进入休眠状态,等count数量的其他线程赶来
int pthread_barrier_wait(pthread_barrier_t *barrier);
12345678

3.6.2バリアアプリケーションの例

マルチスレッド+バリアを使用して数百万のデータを並べ替えると、効率はシングルスレッドの6倍以上になります。

4スレッド制御

現在、Linuxを含む主流のオペレーティングシステムには、プロセスが作成できるスレッドの最大数に明確な制限はありません。ただし、これは制限がないことを意味するのではなく、sysconfを使用して検出できないことを意味します。

4.1スレッド属性

スレッドの属性は、分離状態、スレッドスタックの最小アドレス、スレッドスタックのサイズなど、属性パラメーターpthread_attr_tを介して設定できます。具体的な情報については、Unix環境第3版の高度なプログラミングの第12章p341を参照してください。

4.2同期プロパティ

スレッド同期オブジェクトには、ミューテックス属性、読み取り/書き込みロック属性、条件変数属性、バリア属性などの属性もあります。

  1. ミューテックス属性:pthread_mutexattr_tは、プロセス共有属性、ロバスト属性、およびタイプ属性があることを示します。
  2. 読み取り/書き込みロック属性:pthread_rwlockattr_tは、読み取り/書き込みロックでサポートされる唯一の属性がプロセス共有属性であることを示します。
  3. 条件変数の属性:pthread_condattr_tは、条件変数に2つの属性(プロセス共有属性とクロック属性)があることを示します。
  4. バリア属性:pthread_barrierattr_tは、プロセスのみが属性を共有することを示します。

4.3スレッドとフォーク

スレッドがforkを呼び出すと、子プロセスのプロセスアドレス空間全体のコピーが作成されます。

POSIX.1は、fork returnとexec関数の1つを呼び出す子プロセスの間で、子プロセスは非同期シグナルに対して安全な関数のみを呼び出すことができると述べています。

この制限の理由は、親プロセスのスレッドがfork関数を呼び出して子プロセスを生成し、子プロセス内にスレッドが1つしかないためです(forkスレッドのコピーが呼び出されます)。生成された子プロセスには、親プロセスのアドレス空間全体(親プロセスによって保持されているロックを含む)のコピーがありますが、すべてのスレッドを継承するわけではないため、子プロセスは、保持しているロックと保持しているロックを知る方法がありません。ロックを解除する必要があります。したがって、親プロセスがforkを呼び出し、子プロセスがexecを呼び出してメモリ空間をクリーンアップした後、子プロセスは安全ではありません。この期間中は、非同期信号に対して安全な関数のみを呼び出すことができます。

ロック状態をクリアするには、pthread_atfork関数を呼び出してフォークハンドラーを作成します(関数のプロトタイプと例については、p367を参照してください)。

4.4スレッドとI / O

マルチスレッド環境では、ファイルに対する一般的な読み取りおよび書き込み操作(lseek、読み取り、書き込みなど)が同時に発生すると、相互上書きなどの予期しない結果が生じる可能性があります。これは、同じプロセス内のスレッドが同じファイル記述を共有しているためです。キャラクターによって。この状況を回避するには、pread、pwriteなどのスレッドセーフな読み取りおよび書き込み関数を使用できます。

pwrite()関数は基本的にwrite()と同等ですが、(o_appendが設定されているかどうかに関係なく)ファイルオフセットを変更しないため、スレッドセーフです。

おすすめ

転載: blog.csdn.net/qq_22473333/article/details/113506147