マルチスレッドプログラミング(LinuxのC)

マルチスレッドプログラミングは言うことができるよう、すべてのプログラマの基本的なスキルだけでなく、開発の難しさの一つ、LinuxのCは、例えば、一般的なスレッドの作成と複数のスレッド同期して、最終的には、マルチスレッドプログラミングについてだったこの論文および概要は考え、コード例を与えます。

まず、スレッドを作成

マルチスレッドプログラミングの最初のステップ、スレッドを作成します。スレッドを作成すると、実際には、同じプロセスの複数の同時または並列実行制御フローように制御処理を追加します。

スレッドの作成機能、その他の機能はもはやここに記載されています、あなたは参照することはできませんpthread.h

#include<pthread.h>

int pthread_create(
    pthread_t *restrict thread,  /*线程id*/
	const pthread_attr_t *restrict attr,    /*线程属性,默认可置为NULL,表示线程属性取缺省值*/
	void *(*start_routine)(void*),  /*线程入口函数*/ 
	void *restrict arg  /*线程入口函数的参数*/
	);
复制代码

コード例:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

char* thread_func1(void* arg) {
    pid_t pid = getpid();
    pthread_t tid = pthread_self();
    printf("%s pid: %u, tid: %u (0x%x)\n", (char*)arg, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);

    char* msg = "thread_func1";
    return msg;
}

void* thread_func2(void* arg) {
    pid_t pid = getpid();
    pthread_t tid = pthread_self();
    printf("%s pid: %u, tid: %u (0x%x)\n", (char*)arg, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);
    char* msg = "thread_func2 ";
    while(1) {
        printf("%s running\n", msg);
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    if (pthread_create(&tid1, NULL, (void*)thread_func1, "new thread:") != 0) {
        printf("pthread_create error.");
        exit(EXIT_FAILURE);
    }

    if (pthread_create(&tid2, NULL, (void*)thread_func2, "new thread:") != 0) {
        printf("pthread_create error.");
        exit(EXIT_FAILURE);
    }
    pthread_detach(tid2);

    char* rev = NULL;
    pthread_join(tid1, (void *)&rev);
    printf("%s return.\n", rev);
    pthread_cancel(tid2);

    printf("main thread end.\n");
    return 0;
}
复制代码

第二に、スレッドの同期

時々、スレッド間の同期のために、その後の必要性、複数のスレッドを実行するために互いに協力する必要があります。一般的に使用されるスレッド間の同期方法は以下のとおりです。

  • 排他的
  • セマフォ
  • 条件変数

我々は行われていないスレッド同期の例を見て:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

#define LEN 100000
int num = 0;

void* thread_func(void* arg) {
    for (int i = 0; i< LEN; ++i) {
        num += 1;
    }
    
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, (void*)thread_func, NULL);
    pthread_create(&tid2, NULL, (void*)thread_func, NULL);

    char* rev = NULL;
    pthread_join(tid1, (void *)&rev);
    pthread_join(tid2, (void *)&rev);

    printf("correct result=%d, wrong result=%d.\n", 2*LEN, num);
    return 0;
}
复制代码

結果:correct result=200000, wrong result=106860.

[1]は相互に排他的

重要なリソースを取得することができ、最大1つのスレッドを持つことができ、同じ時間を制限、相互に排他的を通じて、重要なリソースにアクセスする際にこれが最も容易に理解されます。

実際には、相互に排他的論理はこれです:あなたが路上で見つかったリソースにアクセスする場合は、実行中の他のスレッドがロックされたミューテックスを見つけることがあれば、他のスレッドは、重要なリソースへのロック解除、アクセスにロックされていない、スレッドが現在のスレッドにアクセスし、ロック解除を待ってハング重要な資源の後、ロックを解除して、再度実行するようにスケジュールされるのを待って、保留中のミューテックススレッドを覚まします。

「待ちサスペンド」と操作を達成する方法の「待機中のスレッドを覚ましますか」?各ミューテックス待ちキューがあるが、その待ち時間に参加する最初のキューに、ミューテックスにハングアップするためのスレッドを待って、その後、設定されたスレッドの状態は眠り、その後、別のスレッドに切り替えるためにスケジューラ機能を呼び出すことです。キューに参加する準備ができて、単に睡眠準備からその状態を変更するには、キュー内で待機してから削除し、キュー内で待機している他のスレッドをウェイクアップするスレッドが、その後、次回スケジューラ機能は、実行を覚ますために切り替えることが可能ですスレッド。

次のように主な機能は次のとおりです。

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,     
       const pthread_mutexattr_t *restrict attr);       /*初始化互斥量*/
int pthread_mutex_destroy(pthread_mutex_t *mutex);      /*销毁互斥量*/
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
复制代码

ミューテックスは、例えば、計算誤った結果、上記の問題を解決します。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

#define LEN 100000
int num = 0;

void* thread_func(void* arg) {
    pthread_mutex_t* p_mutex = (pthread_mutex_t*)arg;
    for (int i = 0; i< LEN; ++i) {
        pthread_mutex_lock(p_mutex);
        num += 1;
        pthread_mutex_unlock(p_mutex);
    }
    
    return NULL;
}

int main() {
    pthread_mutex_t m_mutex;
    pthread_mutex_init(&m_mutex, NULL);

    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, (void*)thread_func, (void*)&m_mutex);
    pthread_create(&tid2, NULL, (void*)thread_func, (void*)&m_mutex);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_mutex_destroy(&m_mutex);

    printf("correct result=%d, result=%d.\n", 2*LEN, num);
    return 0;
}
复制代码

結果:correct result=200000, result=200000.

あなたが他の相互に排他的で相互に排他的なコードがある入れ子になっている場合は、デッドロックに注意を払う必要があります。

どちらの場合も、デッドロックを生成します。

  • 一つのケースは、次の場合に2回目の呼び出しロックが既に占有されているため、同じスレッドは二回、ロックを呼び出す場合、スレッドは、他のスレッドのリリースのロックを待ってハングアップ、それのロックは彼自身で占められていますスレッドは中断されていないとロックを解除する機会を、したがって、一時停止状態で待機していない、とデッドロックだろう。
  • 別の典型的なデッドロック状況は次のとおりです。スレッドがロック1を取得し、スレッドBがロック2は、その後、呼び出しがロックロック2を取得しようとしているスレッドを取得、結果はロックB 2待機中のスレッドのリリースを中断する必要があり、その後、スレッドBもロック1を取得しようとしてロックを呼び出して、結果はスレッド1を待っているロックの解除を中断する必要があり、その後、そしてBは中断状態に常にあるスレッド。

デッドロックを回避する方法:

  1. ミューテックスなし(このしばしば非常に困難)
  2. 複数のロックを取得中に書かれた手順は避けるべきです。
  3. 我々はそうする必要がある場合は、原則があります。同じ順序ですべてが(アドレスの順番に応じて、一般的にミューテックス変数)は、すべてのスレッドが複数のロック、デッドロックを必要に応じて、ロックを取得します。ロック1は、2ロックするときに使用されるようなプログラムは、ロック3が、それらがアドレス変数に対応しなければならないがミューテックス1をロックされている<ロック2 <3をロックし、その後、すべてのスレッドは、2つまたは3つのロックを取得する必要がありますプレスロック1、ロック2は、3のオーダーのロック配列はロックのすべてを決定することは困難である場合は、使用すべきである。得られるpthread_mutex_trylock代わりに呼び出しpthread_mutex_lock、呼び出しをデッドロックを避けるために。
[2]条件変数

ダウン継続する条件を待つスレッドが存在する(この条件は、他のスレッドによって決定される)、そして今、この条件が満たされていない、スレッドは、実装プロセス操作の他のスレッドまで待ってブロック:条件変数を合計することができます条件が確立され、彼らは継続するスレッドを覚まします。

相関関数は次のよう:

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond,
       const pthread_condattr_t *restrict attr);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex,
       const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
复制代码

最も簡単に条件変数の例を理解するために、「プロデューサー - 消費者」モードでは、処理速度が生産、消費者のスレッドスレッドよりも大きい場合には、キューからデータを取るプロデューサーのスレッドキューの消費者のスレッドにデータを送信しますアプローチは「世論調査」再びいくつかの時間を待つことですが、このアプローチは非常に良いではない、ので、あなたが条件変数は、この問題に良い解決策になることができますどのくらいこの時間がわからないキュー内にデータを生成しません。ここでは、コードは次のようになります。

#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<string.h>

#define LIMIT 1000

struct data {
    int n;
    struct data* next;
};

pthread_cond_t condv = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER; 
struct data* phead = NULL;

void producer(void* arg) {
    printf("producer thread running.\n");
    int count = 0;
    for (;;) {
        int n = rand() % 100;
        struct data* nd = (struct data*)malloc(sizeof(struct data));
        nd->n = n;

        pthread_mutex_lock(&mlock);
        struct data* tmp = phead;
        phead = nd;
        nd->next = tmp;
        pthread_mutex_unlock(&mlock);
        pthread_cond_signal(&condv);

        count += n;

        if(count > LIMIT) {
            break;
        }
        sleep(rand()%5);
    }
    printf("producer count=%d\n", count);
}

void consumer(void* arg) {
    printf("consumer thread running.\n");
    int count = 0;
    for(;;) {
        pthread_mutex_lock(&mlock);
        if (NULL == phead) {
            pthread_cond_wait(&condv, &mlock);
        } else {
            while(phead != NULL) {
                count += phead->n;
                struct data* tmp = phead;
                phead = phead->next;
                free(tmp);
            }
        }
        pthread_mutex_unlock(&mlock);
        if (count > LIMIT)
            break;
    }
    printf("consumer count=%d\n", count);
}

int main() {
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, (void*)producer, NULL);
    pthread_create(&tid2, NULL, (void*)consumer, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    return 0;
}
复制代码

実行論理条件変数:

キーはの実装を理解することでint pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex)、他の人が理解することは比較的容易であるとき、ここで何が起こるかを。他のまで待って、ここまで戻っての判断が満たさ実行条件でない場合は、ロックを解除し、スレッド、この機能を実行する前に条件が満たされた場合、条件が満たされているかどうかを判断するためにmutexを獲得ロックダウンの解除後も継続する必要がありますスレッドの通知条件が満たさロックを解除するためにダウン実行した後、再度、スレッドロックに目覚めさせています。ショート:ロックが解除される- >ブロックが待っている- >ロックが目覚めた後、戻ります

実装の詳細は、ソースコードを参照するpthread_cond_wait.cpthread_cond_signal.cを

上記の例は、以下のサンプルコードがより簡潔であり、複雑になることができます。

#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<string.h>

#define NUM 3
pthread_cond_t condv = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER; 

void producer(void* arg) {
    int n = NUM;
    while(n--) {
        sleep(1);
        pthread_cond_signal(&condv);
        printf("producer thread send notify signal. %d\t", NUM-n);
    }
}

void consumer(void* arg) {
    int n = 0;
    while (1) {
        pthread_cond_wait(&condv, &mlock);
        printf("recv producer thread notify signal. %d\n", ++n);
        if (NUM == n) {
            break;
        }
    }
}

int main() {
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, (void*)producer, NULL);
    pthread_create(&tid2, NULL, (void*)consumer, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    return 0;
}
复制代码

結果:

producer thread send notify signal. 1   recv producer thread notify signal. 1
producer thread send notify signal. 2   recv producer thread notify signal. 2
producer thread send notify signal. 3   recv producer thread notify signal. 3
复制代码

[3]セマフォ

セマフォは、共有リソースのユーザーの数は限られてサポートを制御するのに適しています。0と指定された最大値との間のカウント値を維持します。いったんスレッドが終了するとsemaphore、オブジェクトは、カウンタの値から1を引いを待っている、スレッドが完了したときにsemaphoreリリース対象を、カウントがインクリメントされます。カウント値が0の場合、スレッドは、カウント値が0を超えるまで、待つように中断されます。

次のように主な機能は次のとおりです。

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t * sem);
int sem_destroy(sem_t * sem);
复制代码

次のようにコード例は以下のとおりです。

#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<string.h>
#include<semaphore.h>

#define NUM 5

int queue[NUM];
sem_t psem, csem; 

void producer(void* arg) {
    int pos = 0;
    int num, count = 0;
    for (int i=0; i<12; ++i) {
        num = rand() % 100;
        count += num;
        sem_wait(&psem);
        queue[pos] = num;
        sem_post(&csem);
        printf("producer: %d\n", num); 
        pos = (pos+1) % NUM;
        sleep(rand()%2);
    }
    printf("producer count=%d\n", count);
}

void consumer(void* arg){
    int pos = 0;
    int num, count = 0;
    for (int i=0; i<12; ++i) {
        sem_wait(&csem);
        num = queue[pos];
        sem_post(&psem);
        printf("consumer: %d\n", num);
        count += num;
        pos = (pos+1) % NUM;
        sleep(rand()%3);
    }
    printf("consumer count=%d\n", count);    
} 

int main() {
    sem_init(&psem, 0, NUM);
    sem_init(&csem, 0, 0);

    pthread_t tid[2];
    pthread_create(&tid[0], NULL, (void*)producer, NULL);
    pthread_create(&tid[1], NULL, (void*)consumer, NULL);
    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);
    sem_destroy(&psem);
    sem_destroy(&csem);

    return 0;
}
复制代码

セマフォ実行ロジック:

場合、共有リソースを取得する必要が、セマフォチェック、値が0より大きければ、値ずつ、アクセス、値1を加えた後の共有リソースへのアクセスを、前記ウェイクアップ、セマフォスレッドのためにそこに保留されている場合スレッド、信号は、次に待つために中断し、0であるかどうかを確認します。

ソースを参照してくださいsem_post.c

第三に、マルチスレッドプログラミングの概要と思考

最後に、我々は要約し、マルチスレッドプログラミングの考え方。

  • 最初のポイントは、ほとんどの場合、我々は複数のスレッドを作成しているので、それらが同期していない場合は、問題が発生する可能性があり、一緒に仕事を得ることです、我々はマルチスレッドプログラミングの同期中に問題を検討するために注意を払わなければならないということです。
  • 第二に、デッドロックの問題。複数のスレッドが複数の重要なリソースにアクセスすると、不適切な取り扱いのデッドロックが続きます。あなたは、コンパイラが発生した場合、ランタイムは立ち往生、そこにあなたがそれらの複数のスレッドが重要なリソースへのアクセスを考えることができ、デッドロックが発生したことが、とても速く問題を見つけることができます。
  • 、重要なリソースの処理、マルチスレッドの問題、大きな問題サード複数のスレッドが重要なリソースにアクセスするときので、アクセスやで、スレッドにすべての重要なリソースを扱いますやり方でリサイクルサービス要求のスレッド他のスレッドは、重要なリソースへの一つのスレッドだけアクセスが多くの問題を解決するようにします。
  • 第四に、スレッドプールは、短い多数のタスクを扱うときに、我々は良いスレッドプールを作成することができ、スレッドプールのスレッドがタスクキューからタスクの実行を取るために続けて、そのスレッドと破壊する多数のスレッドを作成しない、ここではもはやありませんディテール。

参考資料:pthread.hの-スレッド

個人的なマイクロチャネル公衆番号に注意を歓迎、行こう!

ここに画像を挿入説明

おすすめ

転載: juejin.im/post/5d15ef19f265da1b8811efee