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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

GCC

GCCはGNU Compiler Collectionの略です。LinuxのC言語コンパイラのデファクトスタンダードであり、数多くの他言語やプラットフォームサポートもします。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

845閲覧

C言語スレッドについて

strike1217

総合スコア651

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

GCC

GCCはGNU Compiler Collectionの略です。LinuxのC言語コンパイラのデファクトスタンダードであり、数多くの他言語やプラットフォームサポートもします。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2017/07/27 11:18

編集2017/07/27 12:44

不明な点が2つほどあります。

まずは、TLSを使うコードについてです。

#define _OPEN_THREADS #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<pthread.h> #define THREADS 3 __thread int tls; int global; void *func(void *arg){ int num = *(int *)arg; tls = num; global = num; sleep(1); printf("Thread = %d tls = %d global = %d\n", num, tls, global); } int main(){ int ret; pthread_t thread[THREADS]; int num; for(num = 0; num < THREADS; num++){ ret = pthread_create(&thread[num], NULL, func, (void *)&num); if(ret){ printf("Error pthread_create\n"); exit(1); } } for(num = 0; num < THREADS; num++){ ret = pthread_join(thread[num], NULL); if(ret){ printf("Error pthread_join\n"); exit(1); } } return 0; }

実行すると・・・毎回、全然違う値がランダムに出現します。

Thread = 0 tls = 0 global = 0 Thread = 1 tls = 1 global = 0 Thread = 2 tls = 2 global = 0 Thread = 2 tls = 2 global = 0 Thread = 0 tls = 0 global = 0 Thread = 2 tls = 2 global = 0 Thread = 2 tls = 2 global = 0 Thread = 2 tls = 2 global = 0 Thread = 0 tls = 0 global = 0

num の値はfor文によっt、0 ~ 2 まで増えていきます・・・
が、なんでこのような結果になるのでしょうか??
ランダムでは困ります。ちゃんと毎回同じような結果が出るようにするにはどうすればよろしいでしょうか??

2,スレッド実行のタイミング

#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> void *func(){ int pid; pthread_t thread_id; thread_id = pthread_self(); fprintf(stdout, "func called\n"); fprintf(stdout, " thread ID = %ld\n", thread_id); pid = getpid(); fprintf(stdout, " 2 : pid = %d\n", pid); } int main(){ pthread_t thread; int pid; fprintf(stdout, "----Program start----\n"); pid = getpid(); fprintf(stdout, "1 : pid = %d\n", pid); if(pthread_create(&thread, NULL, func, NULL) != 0) fprintf(stderr, "Failed to create thread"); printf("pthread_create called : thread ID = %ld\n\n", thread); pthread_join(thread, NULL); return 0; }

結果は以下のようになりました。

----Program start---- 1 : pid = 2740 pthread_create called : thread ID = 140101821789952 func called thread ID = 140101821789952 2 : pid = 2740

「ふ〜〜ん」って感じですが、
Pthreadプログラミング(1)
新しいスレッドを作成する

上記のサイトとは結果が異なります。

(1),プロセスとスレッドでPIDが違っている(私の方では、毎回同じです。)
(2),スレッドの関数の呼び出しタイミングが異なる。

古いサイトなので、(1)の方はおそらくカーネルのバージョンの違いだと思います。
(2)番のほうですね。
プロセススケジューリングはスレッド単位で行われます。
つまり、createされてからjoinされるまで、スレッドの実行タイミングはプログラマは把握できないということでしょうか?

[sched.c / sched.hなど]

inux-4.7.3/arch/powerpc/platforms/cell/spufs/sched.c linux-4.7.3/kernel/time/tick-sched.c linux-4.7.3/net/netfilter/ipvs/ip_vs_sched.c linux-4.7.3/net/sunrpc/sched.c linux-4.7.3/tools/perf/builtin-sched.c linux-4.7.3/tools/perf/tests/evsel-tp-sched.c linux-4.7.3/include/asm-generic/bitops/sched.h linux-4.7.3/include/linux/sched.h linux-4.7.3/include/linux/sunrpc/sched.h linux-4.7.3/include/net/pkt_sched.h linux-4.7.3/include/trace/events/sched.h linux-4.7.3/include/uapi/linux/pkt_sched.h linux-4.7.3/include/uapi/linux/sched.h linux-4.7.3/include/xen/interface/sched.h linux-4.7.3/kernel/sched/sched.h linux-4.7.3/kernel/time/tick-sched.h

スレッドの実行タイミングはスケジューラのアルゴリズムに任せられているわけですよね。

マルチスレッドなどをプログラムする時は、kernel側のスケジューリングアルゴリズムを把握し、スレッドが呼び出されるタイミングを工夫しないといけないということでしょうか??
(どんてもなく大変ですね・・・)

どなたか教えてください。
環境 : Linux Debian系 64bit gcc です。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

まずは、TLSを使うコードについてです。
...
実行すると・・・毎回、全然違う値がランダムに出現します。

この振る舞いとTLSは無関係です。(よくある)スレッド開始処理にまつわる実装バグです。

メインスレッドから新規作成スレッドにパラメータを与える際に、ローカル変数numを更新しながらポインタ渡ししています([B]→[A])。複数スレッドを連続的に作成しているため、メインスレッドでの変数num更新操作のタイミングと、新スレッドでのargを経由した変数numの読取操作が データ競合(data race) します。この状況では、プログラムを実行するたびに各スレッド上での命令実行タイミングが変わるため、プログラムの出力結果もかわってしいます。

C++

1void *func(void *arg){ 2 int num = *(int *)arg; // [A] 3 //... 4} 5 6int main(){ 7 int num; 8 9 for(num = 0; num < THREADS; num++){ 10 ret = pthread_create(&thread[num], NULL, func, (void *)&num); // [B] 11 //... 12 } 13}

つまり、createされてからjoinされるまで、スレッドの実行タイミングはプログラマは把握できないということでしょうか?

はい。それがPOSIXスレッド(thread)の仕様です。

マルチスレッドなどをプログラムする時は、kernel側のスケジューリングアルゴリズムを把握し、スレッドが呼び出されるタイミングを工夫しないといけないということでしょうか??

考え方が逆です。マルチスレッドプログラムでは、各スレッドの 実行タイミングに依存してはいけません。実行タイミングによってプログラマの意図通り振舞わないマルチスレッドプログラムは、単に並行処理設計が適切でない壊れたプログラムと解釈されます。

考慮すべきは"タイミング"ではなく、各スレッド間で扱う"データの依存関係"です。あるスレッドAで計算したデータを、別スレッドBが利用するのであれば、その依存関係に基づいてスレッド間の待機/通知処理(同期処理)として設計・実装されなければなりません。

投稿2017/07/27 15:05

yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/07/28 04:01 編集

rubato6809さんの回答を元にプログラムを作成し直したところ、毎回異なる値が表示されました。 ですが、必ずしも、0, 1, 2という順番に表示されないのは、3つのスレッドの実行タイミング異なるからですよね?? これを必ず0,1,2という順番に表示させる方法って存在するんでしょうか? シリアライザとかでしょうか?
yohhoy

2017/07/28 07:20 編集

> これを必ず0,1,2という順番に表示させる方法って存在するんでしょうか? 存在します。pthreadで実装する場合は、pthread_barrier_waitを用いるか、pthread_cond_wait/pthread_cond_broadcastを利用するのが良いでしょう。 が、真に表示順序が重要なのであれば、そもそもシングルスレッドで動かせばよいのでは?(学習用途ならご自由にいろいろ試せばよいですが)
strike1217

2017/07/28 07:51

「考慮すべきは"タイミング"ではなく、各スレッド間で扱う"データの依存関係"です。」 マルチスレッドの場合はタイミングはどうでも良いんですね!! わかりました。 ありがとうございます。
strike1217

2017/07/29 11:42

rubato6809さんの回答にある一度別の変数に代入すると、毎回異なる値が出現する理由は何でしょうか?
strike1217

2017/07/29 14:21

解決いたしました。
guest

0

疑問を手当たりしだいに出すのではなく、2つの質問を一つの問に含めるのでなく、一つの問いではポイントを一つに絞って、話題が拡散しないようにしてほしいものです。

num の値はfor文によっt、0 ~ 2 まで増えていきます・・・
が、なんでこのような結果になるのでしょうか??

各スレッドに渡すパラメータを、こう安易に渡したのが不具合の原因だろう。

C

1 for (num = 0; num < THREADS; num++) { 2 ret = pthread_create(・・・, (void *)&num);

2番目にcreateしたスレッド、即ち num == 1を渡したつもりのスレッドが、
いざ動き出しnum = *(int*)arg;アクセスした時は、既にmain()側の for 文によってnumの値が2になった後だった、よって tls 変数にも2が代入されてしまった。残念!

一段落を追記。
3つのスレッドは0→1→2→3というnumの変化に同期して生成されるが、同期してるのはあくまで「スケジューリング対象にする」作業でしかない。実際にfunc()が動くタイミングはスケジューラに任されるので、いつ動くか、それは予想がつかないことなのだ。よって0→1→2→3というnumの変化とfunc()の動作は同期しない。

main()側は、各スレッドに渡すパラメータを、それぞれ異なるメモリに格納して渡せば良いだろう。例えばこんな風に。

C

1 int args[THREADS]; // 2 3 for (num = 0; num < THREADS; num++) { 4 args[num] = num; 5 ret = pthread_create(・・・, (void *)&args[num]);

これで3つのスレッドに、0, 1, 2と異なる値を渡せるのではないか。

念の為1:int global変数はTLS(Thread Local Storageと言うのかw)ではない、実体は一個の変数である。3つのスレッドがglobal = num; する都度、値は上書きされる。
その直後、各スレッドはsleep(1); している。眠りから覚めた3つのスレッドがprintf(" ・・・global = %d\n", ・・・ global);で表示するのは、スリープする直前、最後に上書きした値と思われる。

念の為2:create直後も、スリープから目覚めた後も、3つのスレッドがどういう順序で動き出すかは、運を天にまかせるべきことじゃないかな。

投稿2017/07/27 15:10

編集2017/07/29 22:00
rubato6809

総合スコア1380

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/07/28 03:53

おお〜 確かに別々の値が出現しますね。
strike1217

2017/07/28 04:00

「sleep解除後もスケジューリングの対象となる!」ということですね。
rubato6809

2017/07/28 23:19

sleep解除後もスケジューリングの対象・・・当然の事です。
rubato6809

2017/07/28 23:28

スレッドが受け取る引数は、君が示したコードでは「void *arg」だね。 void* という型である意味、そのココロを、理解できてるかな?
strike1217

2017/07/29 11:37

自分がよくわかっていないのは、別の変数に入れる必要性です。 args[num] = num; void* こちらは、どんな型でも受け取れるように・・・ということではないでしょうかね?
rubato6809

2017/07/29 12:34 編集

> よくわかっていないのは、別の変数に入れる必要性 即ち、それは私の説明を読んでも for (・・・; num++)  pthread_create(・・・, &num); では君の期待通りにならない理由が理解できないということか。残念でもあり、申し訳なくもあり、だね。 for文で numの値は0→1→ 2→3 と変化する。では、それと「同期して」スレッドfunc()が動きだす保証があっただろうか? > void* こちらは、どんな型でも受け取れるように その通り。スレッドに渡せるパラメータは数値(0, 1, 2...)だけとは限らない。構造体でも、配列でも、膨大なパラメータであっても渡せるように、そのデータのある「メモリのアドレス」を渡せるようにしたのだ。void* なら、どんな型のポインタ変数とも互換性があるからね。 即ち、構造体でも配列でも…各スレッドは、それぞれ別のデータを受け取れなくてはならない。その時、**同一のメモリを使って**・異なるデータを・安全に・各スレッドに渡せるだろうか?
strike1217

2017/07/29 14:20

! あああ~~ なるほど! 「アクセスした時は、既にmain()側の for 文によってnumの値が2になった後だった」 言いたいことが分かりました。 スレッドが実行された時にはnumの値は変更された後の可能性があるわけですね。 int args[THREADS];このようにしておけば、値は変動せずに常に一定だからいつ実行されても大丈夫なわけですね!!
rubato6809

2017/07/29 21:43

分かってもらえて、よかった。 スレッドはプロセスと同様、スケジューリングの対象である。OSは、そのスレッドを管理できるだけの情報セット(のようなもの)をOS内部に用意する必要がある。 pthread_create()は、create「生成・作る」であって、callでもrunでもない。createは、その「情報セット」をOS内部に作り、スケジューリングできるように仕立てることが目的である。そのままスレッドを走らせたりはしない。 3つのスレッドは0→1→2→3というnumの変化に同期して生成されるが、同期してるのはあくまで「スケジューリング対象にする」作業でしかない。実際にfunc()が動くタイミングはスケジューラに任されるので、いつ動くか、それは予想がつかないことなのだ。よって0→1→2→3というnumの変化とfunc()の動作は同期しない。 sleep(1)にも触れておこう。 sleep(1)で1秒(=1000msec.)後に次のコードが動く、と覚えたら不正確だと思う。1000msec.の間、そのプロセス又はスレッドを「スケジューリング対象から外す」と言うのが正確なところだろう。1000msec.後、スケジューリング対象に戻ることは戻るが、ただちに動くかどうかは周囲の状況次第。実際に1000msec.後に次のコードが動くのか(勿論ただちに動くこともある)、1005msec.後なのか、1017msec.後か・・・でも人間の目から見たら、どれも「1秒後」。大した違いじゃないね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問