マルチスレッド版数独自動生成ソフトC++コードを題材とする超初心者のためのVisual Studio C++講義
第11章 マルチスレッドプログラミング
第7話 シングルスレッド対4スレッド 両コードとそれぞれの実験結果
第5話改良コード
#pragma warning(disable: 4996)//第2編のために必要
#include<iostream>//インクルードファイルiostreamの読み込み
#include<conio.h>//while(!_kbhit());を使うためのお呪い
#include<string> //文字列変数を使えるようにするために組み込む
#include <iomanip> //setprecisionを使えるように組み込む
#include <cmath>//powなどを使うときに必要
#include <ctime>//time()(←現時刻発生する関数)を使うために必要
using namespace std;//coutを使うときに必要なお呪い
#include <process.h>//_beginthreadを使うために必要
void 素数生成1();//8 * m + 1 タイプの素数を生成
void 素数生成2();//8 * m + 3 タイプの素数を生成
void 素数生成3();//8 * m + 5 タイプの素数を生成
void 素数生成4();//8 * m + 7 タイプの素数を生成
int 素数判定(int x);//素数判定関数
int s[4][400000];//8 * m + 2 * a + 1 タイプの素数を収納する
int cn[4] = { 0,0,0,0 };//8 * m + 2 * a + 1 タイプの素数をカウントする変数
int main() {
clock_t hj, ow;
hj = clock();
素数生成1();//8 * m + 1 タイプの素数を生成
素数生成2();//8 * m + 3 タイプの素数を生成
素数生成3();//8 * m + 5 タイプの素数を生成
素数生成4();//8 * m + 7 タイプの素数を生成
ow = clock();
cout << 2 << endl << endl;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < cn[i]; j++) {
cout << s[i][j] << " ";
}
cout << endl << endl;
}
cout << "素数生成時間は" << (double)(ow - hj) / CLOCKS_PER_SEC << "秒です。" << endl;
cout << "生成された素数は総数は" << cn[0] + cn[1] + cn[2] + cn[3] + 1 << "個です。" << endl;
while (!_kbhit());//待機させるための命令
return(0);
}
void 素数生成1() {//8 * m + 1 タイプの素数を生成
for (int i = 0; i < 1250000; i++) {
if (素数判定(8 * i + 1) == 1) {
s[0][cn[0]] = 8 * i + 1;
cn[0]++;
}
}
}
void 素数生成2() {//8 * m + 3 タイプの素数を生成
for (int i = 0; i < 1250000; i++) {
if (素数判定(8 * i + 3) == 1) {
s[1][cn[1]] = 8 * i + 3;
cn[1]++;
}
}
}
void 素数生成3() {//8 * m + 5 タイプの素数を生成
for (int i = 0; i < 1250000; i++) {
if (素数判定(8 * i + 5) == 1) {
s[2][cn[2]] = 8 * i + 5;
cn[2]++;
}
}
}
void 素数生成4() {//8 * m + 7 タイプの素数を生成
for (int i = 0; i < 1250000; i++) {
if (素数判定(8 * i + 7) == 1) {
s[3][cn[3]] = 8 * i + 7;
cn[3]++;
}
}
}
int 素数判定(int x) {//素数判定関数
if (x == 1)return(0);
if (x == 2)return(1);
if (x % 2 == 0)return(0);
int 最後 = sqrt(x);//割り算をする最後の数字
for (int i = 3; i <= 最後; i++) {
if (x % i == 0)return(0);
}
return(1);
}
4スレッドコード
#pragma warning(disable: 4996)//第2編のために必要
#include<iostream>//インクルードファイルiostreamの読み込み
#include<conio.h>//while(!_kbhit());を使うためのお呪い
#include<string> //文字列変数を使えるようにするために組み込む
#include <iomanip> //setprecisionを使えるように組み込む
#include <cmath>//powなどを使うときに必要
#include <ctime>//time()(←現時刻発生する関数)を使うために必要
using namespace std;//coutを使うときに必要なお呪い
#include <process.h>//_beginthreadを使うために必要
void 変身の術関数(void* aa);//スレッドを派生させる関数
int 素数判定(int x);//素数判定関数
const int th = 4;
int 継続[th] = { 1,1,1,1 };
int s[4][400000];//8 * m + 2 * a + 1 タイプの素数を収納する
int cn[4] = { 0,0,0,0 };//8 * m + 2 * a + 1 タイプの素数をカウントする変数
int main() {
clock_t hj, ow;
hj = clock();
int ii[th];
for (int i = 0; i < th; i += 1) {
ii[i] = i;
_beginthread(変身の術関数, 0, &ii[i]); //新しいスレッドを起動して、そのスレッド上で関数問題生成関数を働かせなさいの命令
}
while (1) {
int 合計 = 0;
for (int i = 0; i < th; i++)合計 += 継続[i];
if (合計 == 0)break;
}
ow = clock();
cout << 2 << endl<<endl;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < cn[i]; j++) {
cout << s[i][j] << " ";
}
cout << endl << endl;
}
cout << "素数生成時間は" << (double)(ow - hj) / CLOCKS_PER_SEC << "秒です。" << endl;
cout << "生成された素数は総数は" << cn[0] + cn[1] + cn[2] + cn[3] + 1 << "個です。" << endl;
while (!_kbhit());//待機させるための命令
return(0);
}
void 変身の術関数(void* aa) {//マルチスレッド
int a = *(int*)aa;
for (int i = 0; i < 1250000; i++) {
if (素数判定(8 * i + 2 * a + 1) == 1) {
s[a][cn[a]] = 8 * i + 2 * a + 1;
cn[a]++;
}
}
継続[a] = 0;
}
int 素数判定(int x) {//素数判定関数
if (x == 1)return(0);
if (x == 2)return(1);
if (x % 2 == 0)return(0);
int 最後 = sqrt(x);//割り算をする最後の数字
for (int i = 3; i <= 最後; i++) {
if (x % i == 0)return(0);
}
return(1);
}
両者共に1250回版では差が出なかったので1250000回版にして実験しました。
実験結果
シングル版

4スレッド版

2.651÷0.902 = 2.939
約3倍の速度が得られました。
4倍にならないのはスレッドを起動するに少し時間がかかるからです。
実験回数を増やせば増やすほど4倍に使づいていきますが、
4倍にはなりません。
実験するときには表示部分は外しておいた方がよいでしょう。
表示に時間がかかるからです。
表示は「人間のための処理」なので、とても時間がかかります。
今回は「CPUがどれだけ計算したか」を比べたいので、
いったん表示を止めて“計算だけ”の時間を測ります。
参考までにそれぞれの版のCPU使用率も載せておきます。
シングル版

約12%です。
それに対して4スレッド版は

一瞬で終わってしまうので測定は難しかったのですが、約25%です。
CPU使用率で言うと約2倍です。
そこで生成するのに時間がかかる6次魔方陣の自動生成を
題材に戻し比べてみましょう。
普遍版はすでにできているので、
const int n = 5;//具体的な数字を使うのではなく、 n を使うと汎用性のあるプログラムになる!
const int n = 6;//具体的な数字を使うのではなく、 n を使うと汎用性のあるプログラムになる!
と変更すればよいのですが、
コンソールへの表示は終了時刻を計ってからにしないと、
今回と同じで正確な時間比較ができません。
#include<iostream>//インクルードファイルiostreamの読み込み
#include<conio.h>//while(!_kbhit());を使うためのお呪い
#include<string> //文字列変数を使えるようにするために組み込む
#include <iomanip> //setprecisionを使えるように組み込む
#include <cmath>//powなどを使うときに必要
#include <ctime>//time()(←現時刻発生する関数)を使うために必要
using namespace std;//coutを使うときに必要なお呪い
const int n = 5;//具体的な数字を使うのではなく、 n を使うと汎用性のあるプログラムになる!
void f(int s);//順列生成関数←引数名をgからsに変更(2026年3月19日)
void 2次座標生成();
void 番号づけ確認();
int a[n][n]; //将来n次魔方陣まで生成できるようにn * nに変更
int y[n * n];//縦座標
int x[n * n];//横座標
int cn = 0; //aをnに変更 変更理由はnuberの頭文字nは整数を表す場合が多いからです。
int mg;//魔方陣の対角線等の合計
int main() {
2次座標生成();//y横座標とx縦座標生成
//番号づけ確認();//部屋番号と座標の関連づけが成功しているか確認!
mg = n * (n * n + 1) / 2;
clock_t hj, ow;
hj = clock();
f(0);
ow = clock();
cout << endl << "生成された" << n << "次魔方陣総数は" << cn << "個です。" << endl;//←2026年3月19日訂正
cout << n << "次魔方陣生成時間は" << (double)(ow - hj) / CLOCKS_PER_SEC << "秒です。" << endl;
while (!_kbhit());//待機させるための命令
return(0);
}
void f(int s) {
int h;
for (int i = 0; i < n * n; i++) {
if (s == 0)a[y[s]][x[s]] = i + 1;
h = 1;
if (s > 0) {
for (int j = 0; j < s; j++) {
if (a[y[j]][x[j]] == i + 1) {
h = 0;
break;
}
}
if (h == 1) {
a[y[s]][x[s]] = i + 1;
}
if (h == 1) {
if (y[s] == n - 1 && x[s] == n - 1) {
int g = 0;//魔方陣の対角線または各行または各列の話
for (int j = 0; j < n; j++) {
g += a[j][j];
}
if (g != mg) {
h = 0;
}
}
}
if (h == 1) {
if (y[s] == n - 1 && x[s] == 0) {
int g = 0;//魔方陣の対角線または各行または各列の話
for (int j = 0; j < n; j++) {
g += a[j][n - 1 - j];
}
if (g != mg) {
h = 0;
}
}
}
if (h == 1) {
if (y[s] == 0 && x[s] == n - 2) {
int g = 0;//魔方陣の対角線または各行または各列の話
for (int j = 0; j < n; j++) {
g += a[y[s]][j];
}
if (g != mg) {
h = 0;
}
}
}
if (h == 1) {
if (y[s] > 0 && y[s] < n - 1 && x[s] == n - 1) {
int g = 0;//魔方陣の対角線または各行または各列の話
for (int j = 0; j < n; j++) {
g += a[y[s]][j];
}
if (g != mg) {
h = 0;
}
}
}
if (h == 1) {
if (y[s] == n - 2 && x[s] == 0) {
int g = 0;//魔方陣の対角線または各行または各列の話
for (int j = 0; j < n; j++) {
g += a[j][x[s]];
}
if (g != mg) {
h = 0;
}
}
}
if (h == 1) {
if (y[s] == n - 1 && x[s] > 0 && x[s] < n - 1) {
int g = 0;//魔方陣の対角線または各行または各列の話
for (int j = 0; j < n; j++) {
g += a[j][x[s]];
}
if (g != mg) {
h = 0;
}
}
}
if (h == 1) {
if (y[s] == n - 1 && x[s] == n - 2) {
int g = 0;//魔方陣の対角線または各行または各列の話
for (int j = 0; j < n; j++) {
g += a[y[s]][j];
}
if (g != mg) {
h = 0;
}
}
}
}
if (h == 1) {
if (s + 1 < n * n) {
f(s + 1);
if (n > 4) {
if (cn == 100)return;
}
}
else {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
if (a[j][k] < 10) {
cout << " " << a[j][k] << " ";
}
else {
cout << a[j][k] << " ";
}
}
cout << endl;
}
cout << endl;
cn++;
if (n > 4) {
if (cn == 100)return;
}
}
}
if (n > 4) {
if (cn == 100)return;
}
}
if (n > 4) {
if (cn == 100)return;
}
}
void 2次座標生成() {//y横座標とx縦座標生成
int i, j, c;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
a[i][j] = -1;
}
}
for (i = 0; i < n; i++) {
a[i][i] = i;
}
c = n - 1;
for (i = 0; i < n; i++) {
if (a[i][n - 1 - i] == -1) {
c++;
a[i][n - 1 - i] = c;
}
}
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
if (a[i][j] == -1) {
c++;
a[i][j] = c;
}
}
}
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
x[a[i][j]] = j;
y[a[i][j]] = i;
}
}
}
void 番号づけ確認() {
int i, j;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
if (a[i][j] < 10)cout << " " << a[i][j] << " ";
if (a[i][j] >= 10)cout << a[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
表示させないためには次の部分をまとめて注釈文に変えておくとよいでしょう。
else {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
if (a[j][k] < 10) {
cout << " " << a[j][k] << " ";
}
else {
cout << a[j][k] << " ";
}
}
cout << endl;
}
cout << endl;
cn++;
if (n > 4) {
if (cn == 100)return;
}
( }
まとめて注釈文にする方法について教えます。

(
else {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
if (a[j][k] < 10) {
cout << " " << a[j][k] << " ";
}
else {
cout << a[j][k] << " ";
}
}
cout << endl;
}
cout << endl;
cn++;
if (n > 4) {
if (cn == 100)return;
}
}
)
まとめて注釈文にしたいところを範囲指定します。
Ctrtを押しながk→cの順にキーボードのボタンを押します。

(
/*else {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
if (a[j][k] < 10) {
cout << " " << a[j][k] << " ";
}
else {
cout << a[j][k] << " ";
}
}
cout << endl;
}
cout << endl;
cn++;
if (n > 4) {
if (cn == 100)return;
}
}*/
)
とまとめて注釈文にすることができます。
こういう「一時的に無効化する」テクニックは、
実験するときにとても役に立ちます。
では皆さん第10章第9話の普遍版をマルチスレッド化をしてください。
スレッド数は36です。
スレッド番号0の場合は部屋番号0に0 + 1 = 1 を入れます。
スレッド番号1の場合は部屋番号0に1 + 1 = 2 を入れます。
スレッド番号2の場合は部屋番号0に2 + 1 = 3 を入れます。
・
・
・
スレッド番号rの場合は部屋番号0にr + 1を入れます。
・
・
・
スレッド番号35の場合は部屋番号0に35 + 1 = 36を入れます。
36スレッド版とシングルスレッド版の速度比較をしましょう。