第4話 マルチスレッドはいかにしたら実現できるの?
皆さん、うずうずしてますよね。
早くマルチスレッドのやり方を教えてよ!?・・・
お待たせしました。
いよいよマルチスレッドの実際を説明しましょう。
(以下の説明は、
『初心者 のための VC++による C言語 入門 C++ 入門 基礎から応用まで第2部
第18講 マルチスレッドプログラミング
第3話 新たなスレッドを起動するには?』
を改作したものです。)
マルチスレッド化の方法は、私が知っているだけでも4つもあります。
そのうちの1つの方法は、
_beginthread関数を使用する方法です。
_beginthread関数は、引数を3つもっています。
_beginthread(関数名、新規スレッドのスタック サイズまたは 0、新規スレッドに渡される引数リストまたは NULL)
この中で、
新規スレッドのスタック サイズまたは 0、
新規スレッドに渡される引数リストまたは NULLの部分が?でしょうが、
そこは、0とNULLしておくものだと思ってください。
NULLはデータがないことです。
ですから、基本的に_beginthread関数は
_beginthread(f,0,NULL)と使用すると考えてください。
_beginthread関数を使用するには、process.hをインクルードしておく必要があります。
具体例がないとわかりにくいと思いますので、
新規プロジェクトから次のようにコーティングしてみましょう。
#include<iostream>
#include<process.h>
using namespace std;
void f(void *a);
void g(void *a);
int main(){
_beginthread(f,0,NULL); //新しいスレッドを起動して、そのスレッド上で関数fを働かせなさいの命令
_beginthread(g,0,NULL); //新しいスレッドを起動して、そのスレッド上で関数gを働かせなさいの命令
cout<<"a"<<endl;
}
void f(void *a){
cout<<"b"<<endl;
}
void g(void *a){
cout<<"c"<<endl;
}
void *aの部分が非常に気になるでしょうが、
今のところこれは必要なお呪いだと思ってください。
注釈文に書いてありますが、
_beginthread(f,0,NULL);
で新しいスレッドを起動して、そのスレッド上から関数fを呼び出しなさいという命令を遂行しています。
_beginthread(g,0,NULL);
は同様に新たにスレッドを起動してそのスレッド上に関数gを展開しなさいということです。
_beginthread(f,0,NULL);によって図の上のスレッドが起動して、そのスレッド上で関数fが展開します。
_beginthread(g,0,NULL);によって図の下のスレッドが起動して、そのスレッド上で関数gが働きます。
さて、実行結果ですが・・・
と実行する度に結果が変わってきます。
は納得できるにしても、とについては不思議ですよね。
void g(void *a){
cout<<"c"<<endl;
}
があるのになぜ、cが表示されないのでしょうか。
どうしてこのような奇妙な振る舞いが起きてしまうのでしょうか。
並列処理ですから、3つのスレッドは同時に仕事を始めますが、
ルートスレッドの方が一番先に結果を出した場合には、
bとcが表示される前に、プログラムが終了してしまうため、
となってしまうわけです。
また、の結果は何故引き起こされるのでしょうか。
cout<<"a"<<endl;
ですからすぐに改行されるはずです。
ルートスレッドがaをコンソールに打ち出し次に改行しようとしているときに、
fのbのアウトプットが割り込んでしまったのです。
その後、ルートスレッドの改行とfの改行が続き、
gの仕事がされるためにという奇妙な結果になるわけです。
のように関数fと関数gが仕事を始める前に、
プログラムが終了してしまう事態を防ぐにはどうしたらよいでしょうか。
改善策の1つは、
#include<iostream>
#include<process.h>
using namespace std;
void f(void *a);
void g(void *a);
char t1,t2;
void main(){
t1=1;
t2=1;
_beginthread(f,0,NULL); //新しいスレッドを起動して、そのスレッド上で関数fを働かせなさいの命令
_beginthread(g,0,NULL); //新しいスレッドを起動して、そのスレッド上で関数gを働かせなさいの命令
cout<<"a"<<endl;
while (t1);
while (t2);
}
void f(void *a){
cout<<"b"<<endl;
t1=0;
}
void g(void *a){
cout<<"c"<<endl;
t2=0;
}
参考ダウンロード添付ファイル
とすることです。
while (t1);
while (t2);
によって、関数fと関数gの仕事が終わる前に、
プログラムが終了する事態を防いでいます。
t1とt2は、それぞれ1に初期化されていますから、
各スレッドで0にされるまでは、
whileが永久ループして待ってくれるわけです。
これを6スレッドで探索するには、
7までの素数は表示させておいてから、
9以上の奇数(素数は2以外はすべて奇数)について、
7で割ると1余るタイプ
7で割ると2余るタイプ
7で割ると3余るタイプ
7で割ると4余るタイプ
7で割ると5余るタイプ
7で割ると6余るタイプ
7で割ると7余るタイプ
の6つのタイプに分けて、それぞれのスレッドで探索させれば実現可能です。
(図は、1から1000までの探索になっています。)
ヒントとしてプログラムの途中までのコードを示しておきます。
#include<iostream>
#include<process.h>
#include<math.h>
using namespace std;
void f1(void *a);
void f2(void *a);
void f3(void *a);
void f4(void *a);
void f5(void *a);
void f6(void *a);
char sh(int i);
char t1,t2,t3,t4,t5,t6;
int cn1,cn2,cn3,cn4,cn5,cn6;
long s1[10000000],s2[10000000],s3[10000000],s4[10000000],s5[10000000],s6[10000000];
void main(){
t1=1;
t2=1;
t3=1;
t4=1;
t5=1;
t6=1;
_beginthread(f1,0,NULL); //新しいスレッドを起動して、そのスレッド上で関数f1を働かせなさいの命令
_beginthread(f2,0,NULL); //新しいスレッドを起動して、そのスレッド上で関数f2を働かせなさいの命令
_beginthread(f3,0,NULL); //新しいスレッドを起動して、そのスレッド上で関数f3を働かせなさいの命令
_beginthread(f4,0,NULL); //新しいスレッドを起動して、そのスレッド上で関数f4を働かせなさいの命令
_beginthread(f5,0,NULL); //新しいスレッドを起動して、そのスレッド上で関数f5を働かせなさいの命令
_beginthread(f6,0,NULL); //新しいスレッドを起動して、そのスレッド上で関数f6を働かせなさいの命令
while (t1);
while (t2);
while (t3);
while (t4);
while (t5);
while (t6);
int i;
cout<<"7で割ると1余る素数"<<endl;
for(i=0;i<cn1;i++){
cout<<s1[i]<<" ";
if(i>0 && i%10==0)cout<<endl;
}
cout<<endl;
cout<<"7で割ると2余る素数"<<endl;
for(i=0;i<cn2;i++){
cout<<s2[i]<<" ";
if(i>0 && i%10==0)cout<<endl;
}
cout<<endl;
cout<<"7で割ると3余る素数"<<endl;
for(i=0;i<cn3;i++){
cout<<s3[i]<<" ";
if(i>0 && i%10==0)cout<<endl;
}
cout<<endl;
cout<<"7で割ると4余る素数"<<endl;
for(i=0;i<cn4;i++){
cout<<s4[i]<<" ";
if(i>0 && i%10==0)cout<<endl;
}
cout<<endl;
cout<<"7で割ると5余る素数"<<endl;
for(i=0;i<cn5;i++){
cout<<s5[i]<<" ";
if(i>0 && i%10==0)cout<<endl;
}
cout<<endl;
cout<<"7で割ると6余る素数"<<endl;
for(i=0;i<cn6;i++){
cout<<s6[i]<<" ";
if(i>0 && i%10==0)cout<<endl;
}
}