Linuxプログラミング学習ノート|Linuxマルチスレッド学習[2]-スレッドの同期


スレッドの同期とは
複数のスレッドが共有メモリ領域を同時に読み書きする場合は、このメモリ領域が複数のスレッドに対して一貫していることを保証します.複数のスレッドがこのメモリ領域を同時に読み書きする場合は、スレッドが無効なデータにアクセスしないことを保証するために、いつでも1つのスレッドだけがメモリ領域を変更できるように、スレッドを同期する必要があります.スレッド同期の重要性を次の図で説明します.
この例では、2つのスレッドAとBは、次の3つのことを順番に行います.
  • 変数iをレジスタ
  • に書き込む.
  • レジスタプラス1
  • レジスタ内容を変数i
  • に書き換える.
    スレッドAが先に実行され、スレッドBがスレッドAが2ステップ目まで実行されると、最終変数iの値は2が加算されることが期待されるが、この2つのスレッドは同期されていないため、最終変数iの値は1しか加算されない.したがって,マルチスレッドプログラムにとってスレッドの同期は重要である.
    スレッドの同期が重要である以上、どのような方法で同期できますか?ここでは、3つの基本的なスレッド同期方法について説明します.
  • 反発量(mutex)
  • 読み書きロック
  • 条件変数(cond)
  • はんぱつりょう
    簡単に言えば、反発量は共有メモリ空間をロックするロックであり、それがあれば、同じ時点で1つのスレッドだけがメモリ空間にアクセスできる.1つのスレッドがメモリ空間の反発量をロックすると、他のスレッドは、反発量をロックしたスレッドがこのロックを解除するまで、このメモリ空間にアクセスできません.
    反発量の初期化
    反発量の場合、ロックとロックを解除するには、まず初期化する必要があります.動的割当てと静的割当ての2つの方法を用いて反発量を初期化できた.
    わりあてほうしき
    説明
    どうてきわりあて
    pthread_を呼び出すmutex_Init()関数は、反発メモリ領域を解放する前にpthread_を呼び出すmutex_destroy()関数
    静的割当て
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
    次に、pthread_mutex_init()およびpthread_mutex_lock()の関数のプロトタイプを示します.
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                           const pthread_mutexattr_t *restrict attr);
                           
    args:
        pthread_mutex_t *restrict mutex         :                
        const pthread_mutexattr_t *restrict attr:                   
    
    return:
                 ,0   , 0   
        
    
    
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    
    args:
        pthread_mutex_t *mutex:               
        
    return:
                ,0   , 0   

    反発量の操作
    反発量の基本的な操作は3つあります.
    反発量操作方式
    説明
    pthread_mutex_lock()
    反発量をロックし、反発量がロックされている場合、スレッドがブロックされます.
    pthread_mutex_trylock()
    反発量をロックします.反発量がロックされている場合、スレッドがブロックされません.
    pthread_mutex_unlock()
    ロック解除反発量は、1つの反発量がロックされていない場合、ロック解除がエラーになります.
    上の3つの関数のプロトタイプ:
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    
    args:
        pthread_mutex_t *mutex:               
        
    return:
                ,0   , 0   
        
    
    
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    
    args:
        pthread_mutex_t *mutex:               
    return:
                ,0   , 0   
        
    
    
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    
    args:
        pthread_mutex_t *mutex:               
        
    return:
                ,0   , 0   

    デッドロック
    反発量の使用が適切でないと、デッドロック現象を引き起こす可能性があります.デッドロックとは、2つ以上のスレッドが実行中にリソースを争うことによって互いに待つ現象を指す.例えば、スレッド1はリソースAをロックし、スレッド2はリソースBをロックする.スレッド1にリソースBをロックさせ、スレッド2にリソースAをロックさせます.リソースAとBはスレッド1と2にロックされているため、スレッド1と2はブロックされ、相手のリソースの解放を永遠に待っています.
    デッドロックの発生を避けるために、私たちは以下の点に注意しなければならない.
  • 共有リソースへのアクセスには、
  • のロックが必要です.
  • 反発量の使用が完了すると
  • を破棄する必要がある.
  • ロックをかけた後、必ずロックを解除しなければならない
  • 反発量ロックの範囲は
  • より小さい.
  • の反発量は
  • 少ないはずです.
    リードライトロック
    読み書きロックは反発量と似ているが,より高い並列性を有する.反発量はロックとロック解除の2つの状態しかありませんが、読み書きロックは読み書きロック、書き込みロック、非ロックの3つの状態を設定できます.書き込みロック状態の場合、いつでも1つのスレッドが書き込みロック状態を占める読み書きロックしかありません.一方、リード・ロック状態では、任意の時点で複数のスレッドがリード・ロック状態のリード・ライト・ロックを有することができる.以下に、読み書きロックの機能を示します.
    とくせい
    説明
    1
    読み書きロックが書き込みロック状態である場合、このロックがロック解除される前に、このロックをロックしようとするすべてのスレッドがブロックされます.
    2
    リード・ライト・ロックがリード・ロック状態である場合、リード・ロック状態にあるすべてのスレッドがロックされます.
    3
    読み書きロックが読み書きロック状態である場合、すべてのスレッドがロックを解除するまで、書き込みロック状態にあるすべてのスレッドをブロックする必要があります.
    4
    リード・ライト・ロックがリード・ロック状態である場合、スレッドがライト・モードでロックしようとすると、リード・ライト・ロックは、その後のリード・モード・ロック要求をブロックする.
    読み書きロックの初期化
    反発量と同様に、ロックとロックを解除するには、読み書きロックを初期化する必要があります.読み書きロックを初期化するには、反発量と同様にpthread_rwlock_init()関数を使用します.読み書きロックメモリ領域を解放する前に、pthread_rwlock_destroy()関数を呼び出して読み書きロックを破棄する必要があります.
    次に、pthread_rwlock_init()およびpthread_rwlock_destroy()の関数のプロトタイプを示します.
    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                            const pthread_rwlockattr_t *restrict attr);
                            
    args:
        pthread_rwlock_t *restrict rwlock:                        
        const pthread_rwlockattr_t *restrict attr:                  
    
    return:
                 ,0   , 0   
    
        
    
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    
    args:
        pthread_rwlock_t *rwlock:               
    
    return:
                ,0   , 0   

    読み書きロックの操作
    反発量と同様に、読み書きロックの操作もブロックと非ブロックに分けられます.まず、読み書きロックにどのような基本的な操作があるかを見てみましょう.
    リードライトロック操作方式
    説明
    int pthread_rwlock_rdlock()
    読み書きロック読み書きロック他のスレッドがブロックされます
    int pthread_rwlock_tryrdlock()
    読み書きロック読み書きロック他のスレッドをブロックしない
    int pthread_rwlock_wrlock()
    読み取りと書き込みのロックと書き込みのロックにより、他のスレッドがブロックされます.
    int pthread_rwlock_trywrlock()
    他のスレッドをブロックしない読み書きロックと書き込みロック
    int pthread_rwlock_unlock()
    読み書きロック解除
    以下に、これらの関数のプロトタイプを示します.
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    
    args:
        pthread_rwlock_t *rwlock:              
    
    return:
                ,0   , 0   
    
    
    
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    
    args:
        pthread_rwlock_t *rwlock:              
    
    return:
                ,0   , 0   
    
    
    
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    
    args:
        pthread_rwlock_t *rwlock:               
    
    return:
                ,0   , 0   
    
    
    
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    
    args:
        pthread_rwlock_t *rwlock:               
    
    return:
                ,0   , 0   
        
    
    
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
    
    args:
        pthread_rwlock_t *rwlock:               
    
    return:
                ,0   , 0   

    じょうけんへんすう
    条件変数は反発量とともに用いられる.もし1つのスレッドが反発量にロックされているが、このスレッドが何もできない場合、私たちは反発量を解放して他のスレッドを動作させるべきで、この場合、条件変数を使用することができます.スレッドがシステムが何らかの状態にあるのを待つ必要がある場合は、条件変数も使用できます.
    条件変数の初期化
    相互反発量と同様に、条件変数は動的割当てと静的割当てを使用して初期化できます.
    わりあてほうしき
    説明
    どうてきわりあて
    pthread_を呼び出すcond_Init()関数は、条件変数のメモリ空間を解放する前にpthread_を呼び出す必要があります.cond_destroy()関数
    静的割当て
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER
    次に、pthread_cond_init()およびpthread_cond_destroy()の関数のプロトタイプを示します.
    int pthread_cond_init(pthread_cond_t *restrict cond,
                          const pthread_condattr_t *restrict attr);
                          
    args:
        pthread_cond_t *restrict cond          :                
        const pthread_condattr_t *restrict attr:                  
        
    return:
                  ,0   , 0   
    
    
    
    int pthread_cond_destroy(pthread_cond_t *cond);
    
    args:
        pthread_cond_t *cond:                
    
    return:
                 ,0   , 0   

    条件変数の操作
    条件変数の動作は待機と起動に分けられ、待機動作の関数はpthread_cond_wait()pthread_cond_timedwait()である.起動動作の関数は、pthread_cond_signal()およびpthread_cond_broadcast()である.pthread_cond_wait()がどのように使用されているかを見てみましょう.次は関数のプロトタイプです.
    int pthread_cond_wait(pthread_cond_t *restrict cond,
                          pthread_mutex_t *restrict mutex);
                          
    args:
        pthread_cond_t *restrict cond  :               
        pthread_mutex_t *restrict mutex:           
    
    return:
        0   , 0   

    1つのスレッドがpthread_cond_wait()を呼び出すと、条件変数と反発量が入力され、この反発量はロックされなければならない.この2つのパラメータが入力されると、
  • スレッドは、待機条件のスレッドリストの
  • に配置される.
  • 相互反発量は、ロック解除された
  • .
    この2つの操作はいずれも原子操作である.この2つの操作が終了すると、他のスレッドが動作します.条件変数が真である場合,システムはこのスレッドに戻り,関数は戻り,反発量は再びロックされる.
    待機中のスレッドを起動する必要がある場合は、スレッドの起動関数を呼び出す必要があります.次は関数のプロトタイプです.
    int pthread_cond_signal(pthread_cond_t *cond);
    
    args:
        pthread_cond_t *cond:               
        
    return:
        0   , 0   
    
    
        
    int pthread_cond_broadcast(pthread_cond_t *cond);
     
    args:
        pthread_cond_t *cond:                
        
    return:
        0   , 0   
    pthread_cond_signal()pthread_cond_broadcast()の違いは、前者が1つの待機条件のスレッドを起動するために使用され、後者がすべての待機条件のスレッドを起動するために使用されることである.
    まとめ
    この論文では,マルチスレッドにおける同期の重要性とスレッド同期の3つの方法を主に紹介した.次の記事では、コードでマルチスレッドを使用する方法をプログラムインスタンスで説明します.
    もし本文があなたに役に立つと思ったら、もっと支持してください.ありがとうございます.