LoginSignup
2
2

More than 1 year has passed since last update.

[C言語] pthread/mutexを使った並列処理

Last updated at Posted at 2021-07-24

pthreadとmutexを使い、データの競合を起こさずに並列処理をする方法について調べてみました。

まずはptreadを用いた並列処理のサンプルです。
2つのスレッドでそれぞれ1万回ずつインクリメントすることで最終的にcntの値を20000にします。

#include <stdio.h>
#include <pthread.h>

int cnt = 0;

void *routine(void *p)
{
    for (int i = 0; i < 10000; i++)
        cnt++;
    return (NULL);
}

int main(void)
{
    pthread_t p1, p2;

    // 2つのスレッドで並列処理する
    pthread_create(&p1, NULL, &routine, NULL);
    pthread_create(&p2, NULL, &routine, NULL);

    // 終了するまで待つ
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    printf("cnt -> %d\n", cnt);
}

実行結果

$ gcc test.c
$ ./a.out 
cnt -> 19677
$ ./a.out 
cnt -> 15331
$ ./a.out 
cnt -> 12060

おかしいですね。
結果が実行するたびに変わってしまいます。

実はこのままだとデータの競合が起きてしまい、正しい結果が得られません。
そのため、mutexを使うことで同時アクセスを防ぎます。

#include <stdio.h>
#include <pthread.h>

int cnt = 0;
pthread_mutex_t mutex;

void *routine(void *p)
{
    for (int i = 0; i < 10000; i++)
    {
        pthread_mutex_lock(&mutex); //lockして同時アクセスを防ぐ
        cnt++;
        pthread_mutex_unlock(&mutex);
    }
    return (NULL);
}

int main(void)
{
    pthread_t p1, p2;
    pthread_mutex_init(&mutex, NULL);

    // 2つのスレッドで並列処理する
    pthread_create(&p1, NULL, &routine, NULL);
    pthread_create(&p2, NULL, &routine, NULL);

    // 終了するまで待つ
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    pthread_mutex_destroy(&mutex);
    printf("cnt -> %d\n", cnt);
}

実行結果

$ gcc test.c
$ ./a.out 
cnt -> 20000
$ ./a.out 
cnt -> 20000
$ ./a.out 
cnt -> 20000

無事に正しい結果が得られました。
mutex_lockをすることで同時に別のスレッドがアクセスするのを防いでくれるみたいですね。
ちなみに値の変更だけではなく、参照する際もロックが必要です。

そのため次のような条件文でアクセスする際も必要となります。

#include <stdio.h>
#include <pthread.h>

int cnt = 0;
pthread_mutex_t mutex;

void *routine(void *p)
{
    for (int i = 0; i < 10000; i++)
    {
        pthread_mutex_lock(&mutex);     // lockして同時アクセスを防ぐ
        if (cnt == 10000)               // 参照する際もlockが必要
        {
            pthread_mutex_unlock(&mutex);
            break ;
        }
        cnt++;
        pthread_mutex_unlock(&mutex);
    }
    return (NULL);
}

int main(void)
{
    pthread_t p1, p2;
    pthread_mutex_init(&mutex, NULL);

    // 2つのスレッドで並列処理する
    pthread_create(&p1, NULL, &routine, NULL);
    pthread_create(&p2, NULL, &routine, NULL);

    // 終了するまで待つ
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    pthread_mutex_destroy(&mutex);
    printf("cnt -> %d\n", cnt);
}

実行結果

$ gcc test.c
$ ./a.out 
cnt -> 10000
$ ./a.out 
cnt -> 10000
$ ./a.out 
cnt -> 10000

正しく10000で処理を終了できましたね。

まとめ

無事安全に並列処理を実行することができました。
gcc -fsanitize=threadでコンパイルしてあげると、データ競合のリスクがある際にエラーを出してくれるので便利です。
共用の変数にアクセスする際はロックを忘れないようにしたいですね。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2