質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.10%

実行環境の違いによるスレッドの動作の違いについて

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,799

johejo

score 12

前提

C言語でスレッドの勉強をしています。
練習がてら次のようなプログラムを作成しました。

コマンドライン引数で合計カウント数とスレッド数を入力し実行すると、スレッドごとに0からカウントを始めてすべてのスレッドのカウントの総和が入力した合計カウント数に達すると、プログラムの実行にかかった時間を表示するプログラムです。
標準入出力にスレッド番号とそのスレッドが現在カウントできている値を表示させるようにしています。

スレッド数を増やすと動作がどのように変化するのか、また実行環境を変えると動作が変化するのかを確かめたくてこのようなプログラムを作成してみました。

発生している問題

プログラム自体はおそらく完成しているのですが、実行環境のせいかMacの標準のターミナル上ではうまく動作するものの、WindowsのCygwinターミナル上ではうまく動作しません。

デバッグしてみたところ、どうやら私のCygwin上ではスレッド数が4を超えると5つめからのスレッドのカウントが私の想像通りには始まっていないことはわかりました。

MacのXcodeに付属するCコンパイラでコンパイルして、ターミナルで実行すると想像通りの動作をしました。

勉強中の拙いソースコードですが、なぜこのようなことが起こるのか教えていただけないでしょうか。

実行例について

スレッド数4で実行した場合
$./a.out 100 4
begin 100 4
Thread0 1
Thread1 1
Thread2 1
Thread3 1
Thread3 2
Thread1 2
Thread2 2
Thread0 2
Thread1 3
Thread3 3
Thread2 3
Thread0 3
===中略===
Thread3 24
Thread1 24
Thread2 24
Thread0 24
Thread3 25
Thread1 25
Thread0 25
Thread2 25
sum 100
12.109[sec]

スレッド数5で実行した場合
$./a.out 100 5
begin 100 5
Thread0 1
Thread1 1
Thread2 1
Thread3 1
Thread1 2
Thread3 2
Thread0 2
Thread2 2
Thread1 3
Thread3 3
Thread0 3
Thread2 3
===中略===
Thread1 95
Thread3 95
Thread2 96
Thread0 97
Thread1 96
Thread3 96
Thread2 97
Thread0 98
Thread1 97
Thread4 1
Thread3 97
Thread2 98
sum 391
38.390[sec]

該当のソースコード

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

static pthread_mutex_t MyMutex=PTHREAD_MUTEX_INITIALIZER;

void *thread_func(void *threadArgs);

struct ThreadArgs
{
    int *count_num;
    int *n;
    int *sum;
    int thread_id;
};

void *thread_func(void *threadArgs) {
    int i, thread_id;
    int *n, *count_num, *sum;

    pthread_detach(pthread_self());

    n = ((struct ThreadArgs *) threadArgs) -> n;
    sum = ((struct ThreadArgs *) threadArgs) -> sum;
    count_num = ((struct ThreadArgs *) threadArgs) -> count_num;
    thread_id = ((struct ThreadArgs *) threadArgs) -> thread_id;

    while ( *sum < *count_num) {
        (*n)++;
        printf("Thread%d %d\n", thread_id, *n);
        for (i = 0; i < 50000000; i++) {}
    }

    free(threadArgs);
}

int main(int argc, char *argv[])
{
    int i, j, *n, *top, count_num, thread_num, sum;
    clock_t time1, time0;
    double diff;
    struct ThreadArgs *threadArgs;
    pthread_t mythread;
    int thread_id;

    if (argc != 3) {
          exit(1);
      }

    count_num = atoi(argv[1]);
    thread_num = atoi(argv[2]);

    printf("begin %d %d\n", count_num, thread_num);

    n = (int *)malloc(sizeof(int) * thread_num);

    top = n;

    for (i = 0, n = top; i < thread_num; i++, n++) {
        *n = 0;
    }

    sum = 0;

    time0 = clock();

    thread_id = 0;

    for (i = 0, n = top; i < thread_num; i++, n++) {    
        if((threadArgs = (struct ThreadArgs *)malloc(sizeof(struct ThreadArgs))) == NULL){
            fprintf(stderr, "malloc failed¥n"), exit(1);
        }

        threadArgs -> count_num = &count_num;
        threadArgs -> n = n;
        threadArgs -> sum = &sum;
        threadArgs -> thread_id = thread_id;

        if(pthread_create(&mythread, NULL, thread_func, (void *) threadArgs) != 0){
            fprintf(stderr, "pthread_create() failed¥n"), exit(1);
        }

        thread_id++;
    }

    while (sum < count_num) {
        pthread_mutex_lock(&MyMutex);
        for (i = 0, sum = 0, n = top; i < thread_num; i++, n++) {
            sum += *n;
        }
        pthread_mutex_unlock(&MyMutex);
        //printf("%d %d\n", sum, count_num);
    }
    printf("sum %d\n", sum);

    time1 = clock();

    diff =(double)(time1 - time0) / CLOCKS_PER_SEC;

    printf("%.3f[sec]\n", diff);

    free(top);

    return 0;
}

追加の質問内容

なぜスレッド数5の実行例では、Thread 4(5つめのスレッド)がすぐに開始されないのでしょうか。

追記

排他処理を追加しました。
追加の質問内容と実行例について加筆しました。

補足情報(言語/FW/ツール等のバージョンなど)

実行環境
Windows10 Home (intel Core i5 4460)
Cygwin (gnupack_devel-13.06-2015.11.08)
GCC 4.9.3

macOS Sierra 10.12.1 (Core2Duo SU9400)

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • KSwordOfHaste

    2016/12/23 08:58

    「スレッドのカウントが私の想像通りには始まっていない」点が何かをより具体的に述べるか少し出力例を示して説明すると閲覧者の人たちにわかりやすくなると思います。

    キャンセル

  • yohhoy

    2016/12/23 11:24 編集

    あなたが期待する動作/実行結果を明記下さい。(プログラムには排他制御の不足によるデータ競合と、そもそもの設計からして決定的動作=同じ結果がえられる保証が無い問題の2つがあるように見えます)

    キャンセル

回答 3

checkベストアンサー

+1

mainを実行しているメインスレッドになかなか実行権が渡されないので、 while (sum < count_num) がなかなか処理されないまま、子スレッドが処理し続けている・あるいは既に実行を終了していることが考えられます。スレッドへ実行権を与える・剥奪する処理は、OSに強く依存し、そのOSが独自のスケジューリング・アルゴリズムを用いて、ハードウェア構成も含めたシステム全体の都合によってなされます。したがって、スレッドの実行順序は全く保障されません。「想像通りに」動く方が不思議で、特にシステム負荷が重い場合は複雑な挙動をします。

よってマルチスレッドプログラミングでは、スケジューリングに依存しない書き方が必要で、どうしても実行順序を決めたいときはセマフォなど「同期オブジェクト」を用いてスレッド同士の「待ち合わせ」を行うことが通例です。

追加

セマフォ2つを使った同期を示します。
そこそこまともに動きます。

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>

struct ThreadArgs
{
    int *count_num;
    int *n;
    int *sum;
    int thread_id;
    sem_t* sem4n;
    sem_t* sem4sum;
};

void *thread_func(void *threadArgs);

void *thread_func(void *threadArgs) {
    int i, thread_id;
    int *n, *count_num, *sum;
    sem_t* sem4n;
    sem_t* sem4sum;

    pthread_detach(pthread_self());

    struct ThreadArgs *args = (struct ThreadArgs *) threadArgs;

    n         = args -> n;
    sum       = args -> sum;
    count_num = args -> count_num;
    thread_id = args -> thread_id;
    sem4n     = args -> sem4n;
    sem4sum   = args -> sem4sum;

    free(threadArgs);

    while ( *sum < *count_num) {
        (*n)++;
        printf("Thread%d %d\n", thread_id, *n);

        /* 共有データ*nを使用したので、セマフォの加算 */
        sem_post(sem4n);

        for (i = 0; i < 50000000; i++) {}

        /* 共有データ*sum, *count_numが使えるようになるまで、セマフォの値によるブロック */
        sem_wait(sem4sum);
    }
}

int main(int argc, char *argv[])
{
    int i, j, *n, *top, count_num, thread_num, sum;
    clock_t time1, time0;
    double diff;
    struct ThreadArgs *threadArgs;
    pthread_t mythread;
    int thread_id;
    static sem_t sem4n;
    static sem_t sem4sum;

    if (argc != 3) {
          exit(1);
    }

    count_num = atoi(argv[1]);
    thread_num = atoi(argv[2]);

    printf("begin %d %d\n", count_num, thread_num);

    n = (int *)malloc(sizeof(int) * thread_num);

    /* セマフォの初期化 */
    if (sem_init( &sem4n, 0, 0) != 0) {
        fprintf(stderr, "sem_init() failed\n");
        exit(1);
    }
    if (sem_init( &sem4sum, 0, 0) != 0) {
        fprintf(stderr, "sem_init() failed\n");
        exit(1);
    }

    top = n;

    for (i = 0, n = top; i < thread_num; i++, n++) {
        *n = 0;
    }

    sum = 0;

    time0 = clock();

    thread_id = 0;

    for (i = 0, n = top; i < thread_num; i++, n++) {    
        if((threadArgs = (struct ThreadArgs *)malloc(sizeof(struct ThreadArgs))) == NULL){
            fprintf(stderr, "malloc failed?n"), exit(1);
        }

        threadArgs -> count_num = &count_num;
        threadArgs -> n = n;
        threadArgs -> sum = &sum;
        threadArgs -> thread_id = thread_id;
        threadArgs -> sem4n = &sem4n;
        threadArgs -> sem4sum = &sem4sum;

        if(pthread_create(&mythread, NULL, thread_func, (void *) threadArgs) != 0){
            fprintf(stderr, "pthread_create() failed\n"), exit(1);
        }

        thread_id++;
    }

    while (sum < count_num) {
        for (i = 0, sum = 0, n = top; i < thread_num; i++, n++) {
            /* 共有データ*nが使えるようになるまで、セマフォの値によるブロック */
            sem_wait(&sem4n);
            sum += *n;

            /* 共有データsum, count_numを使用したので、セマフォの加算 */
            sem_post(&sem4sum);
        }
        //printf("%d %d\n", sum, count_num);
    }

    /* セマフォの削除 */
    sem_destroy(&sem4n);
    sem_destroy(&sem4sum);

    printf("sum %d\n", sum);

    time1 = clock();

    diff =(double)(time1 - time0) / CLOCKS_PER_SEC;

    printf("%.3f[sec]\n", diff);

    return 0;
}

begin 1000 20
Thread0 1
Thread1 1
Thread2 1
Thread3 1
Thread4 1
Thread5 1
Thread6 1
Thread7 1
Thread8 1
Thread9 1
Thread10 1
Thread11 1
Thread12 1
Thread13 1
Thread14 1
Thread15 1
Thread16 1
Thread17 1
Thread18 1
Thread19 1
Thread0 2
Thread2 2
Thread5 2
Thread1 2
Thread3 2
Thread6 2
Thread4 2
Thread7 2
Thread9 2
Thread8 2
Thread10 2
Thread11 2
Thread12 2
Thread14 2
Thread13 2
Thread15 2
Thread16 2
Thread17 2
Thread18 2
Thread2 3
Thread6 3
Thread10 3
Thread5 3
Thread0 3
===中略===
Thread5 61
Thread1 61
Thread9 61
Thread17 40
Thread13 61
Thread0 52
Thread4 52
Thread8 52
Thread12 52
Thread2 54
Thread16 52
Thread18 51
Thread6 54
Thread5 62
Thread10 54
Thread1 62
Thread14 54
Thread9 62
Thread13 62
Thread19 40
Thread0 53
Thread3 41
Thread4 53
Thread7 41
Thread8 53
sum 1010
128.482[sec]

環境
Windows 7 Professional SP1
Intel Core i5 750
Cygwin64
GCC 5.4.0

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/12/23 23:20

    サンプルコードまで本当にありがとうございました。
    大学の授業で感じた疑問をプログラムにしてみたところ、動作に対する更なる疑問を生じさせてしまいましたが、解決できてよかったと思います。
    マルチスレッドの場合は単にプログラムを書くだけでなく予期しない動作が起こりうることも考えながら、注意しようと思います。

    他の解答者の方々もありがとうございました。

    キャンセル

0

struct ThreadArgsのメンバにはthread_id以外はポインタで、それはすべてのスレッドで同じオブジェクト(特にsum)を参照しているので不可解な動作になる、と私は想像します。Xcodeでも最適化をONにしてビルドすれば、cygwinと似た挙動になるのではないでしょうか。

複数(メインスレッド含)のスレッドが同じ資源を扱う時には「排他制御」が必要です。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

スレッドに対するCPUの割り当てが異なる事により、発生している様です。
5スレッドの場合に、CPUの割り当てが大きく変化している様に見られます。
実際に正しいものの中でも、各スレッドが起動順に結果が現れない場合が見れます。ただ五千万回のwhileループが行われている内に追いついている様子です。
Linux環境でもスレッド数を多くするとスレッド間で遅れが発生して同様な現象が起こります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.10%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る