並行処理について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
マーチ

並行処理について

#1

投稿記事 by マーチ » 11年前

自らの力では限界と感じたため、再びお力をお貸しいただければと思い書き込ませていただきました。

指定した時間にメッセージを表示し続けるプロセス、endを入力すると処理を終えるプロセス、
この2つを並列処理として動かすプログラムを作っております。

問題点
・endで子プロセスが終わらない
・getsにすればendで子プロセスは終わるが、親プロセスが終わらない
・子プロセスを破棄?する方法がわからない
・「終了する場合はendを入力してください」が最初に何故か2回出る

コードを書き込ませていただきます。
プログラミングがわかっていない初心者ですが、何卒ご指摘助言よろしくお願いいたします。

コード:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

int main()
{
	//プロセスID
	int         p_id;

	int         status;
	int         return_code = 0;
	//フラグ
	int f = 0;

	char message[40];
	//時間
	double startTime, endTime;
	double totalTime = 0.0, setTime = 0.0;

	printf("\nメッセージは何にしますか?\n");
	fgets(message, sizeof(message), stdin);

	/* 時間をセット */
	printf("何ミリ秒にセットしますか?\n");
	scanf("%lf", &setTime);

	//子プロセスを起動する関数fork
	if ((p_id = fork()) == 0) {
		/* 子プロセス */
		printf("子プロセス開始\n");

		char buff[40];    /* 入力文字列 */

			printf("終了する場合はendを入力してください\n");    /* プロンプト表示 */
			//gets(buff);            /* 文字列入力 */
			fgets(buff, sizeof(buff), stdin);

		while(1){
			if(!strcmp(buff, "end"))    /* 文字列比較 */
				break;    /* ループ脱出 */
			else
			printf("終了する場合はendを入力してください\n");    /* プロンプト表示 */
			fgets(buff, sizeof(buff), stdin);
			//gets(buff);            /* 文字列入力 */
		}

		printf("子プロセス終了\n");
		//フラグ
		f = 1;
	}
	else {
		/* 親プロセス */
		while(1){
			/* タイマー開始(ミリ秒単位) */
			startTime = clock();

			while(1){
				endTime = clock();
				totalTime = ((endTime - startTime)*1000) / CLOCKS_PER_SEC;
				if(totalTime >= setTime) break;

			}
			printf("%s", message);
			//フラグ
			if(f == 1)break;

			//時間確認用
			printf("startTime = %f\n", startTime);
			printf("endTime = %f\n", endTime);
			printf("totalTime = %f\n", totalTime);

			//時間初期化
			totalTime = 0.0;
		}

			if (p_id != -1) {
				wait(&status);
				printf("親プロセス終了\n");
			}
			else {
				perror("親プロセス : ");
				return_code = 1;
			}
		return return_code;
	}
	return(0);
}

アバター
ookami
記事: 214
登録日時: 14年前
住所: 東京都

Re: 並行処理について

#2

投稿記事 by ookami » 11年前

4つ同時に答えるのが難しいのですが、
一番のポイントとして、
子プロセスをforkで起動した時、変数fのコピーが作られますが、メモリ空間を共有しているわけではないので、
子プロセス上で f=1 としても、親プロセス上での fは0のままです。

子プロセスが終了するのを待つにはwaitpid()を使うのがよいようです。
http://www.ne.jp/asahi/hishidama/home/tech/c/fork.html

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: 並行処理について

#3

投稿記事 by みけCAT » 11年前

直接は関係なさそうですが、forkのエラーチェックをしていないようですね。
最近見たので貼っておきます。
本の虫: fork()は失敗するんだぜ、覚えときな
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

かずま

Re: 並行処理について

#4

投稿記事 by かずま » 11年前

マーチさんのコードと、どこが違うのか説明してください。

コード:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
 
int main(void)
{
    int p_id;
    int status;
 
    char message[256];
    double startTime, endTime;
    double totalTime = 0.0, setTime = 0.0;
 
    printf("\nメッセージは何にしますか?\n");
    fgets(message, sizeof(message), stdin);
 
    printf("何ミリ秒にセットしますか?\n");
    scanf("%lf", &setTime);
    while (getchar() != '\n') ;  // 改行コードまで読み飛ばす
 
    p_id = fork();
    if (p_id == -1) return 1;

    if (p_id == 0) { /* 子プロセス */
        printf("子プロセス開始\n");
        while (1) {
            char buff[256];
            printf("終了する場合はendを入力してください\n");
            fgets(buff, sizeof(buff), stdin); // buff には改行コードが含まれる
            if (!strncmp(buff, "end", 3)) break;
        }
        printf("子プロセス終了\n");
    }
    else { /* 親プロセス */
        while (1) {
            startTime = clock();
            while (1) {
                endTime = clock();
                totalTime = ((endTime - startTime)*1000) / CLOCKS_PER_SEC;
                if (totalTime >= setTime) break;
            }
            printf("%s\n", message);
            printf("startTime = %f\n", startTime);
            printf("endTime = %f\n", endTime);
            printf("totalTime = %f\n", totalTime);
            if (waitpid(p_id, &status, WNOHANG) == p_id) break;
        }
        printf("親プロセス終了\n");
    }
    return 0;
}
親プロセスの totalTime を求める whileループは busy loop で、
CPU を使いすぎるので、よろしくありません。
usleep を使うことを検討してもいいのでは?

マーチ

Re: 並行処理について

#5

投稿記事 by マーチ » 11年前

皆様返信ありがとうございます!

[quote="ookami" id=3,15604#review,124119]
4つ同時に答えるのが難しいのですが、
一番のポイントとして、
子プロセスをforkで起動した時、変数fのコピーが作られますが、メモリ空間を共有しているわけではないので、
子プロセス上で f=1 としても、親プロセス上での fは0のままです。

子プロセスが終了するのを待つにはwaitpid()を使うのがよいようです。
http://www.ne.jp/asahi/hishidama/home/tech/c/fork.html
[/quote]

>>共有しているわけではないのですね、根本的なところがわかっていませんでした・・・

[quote="みけCAT" id=3,15604#postingbox,124120]
直接は関係なさそうですが、forkのエラーチェックをしていないようですね。
最近見たので貼っておきます。
本の虫: fork()は失敗するんだぜ、覚えときな
[/quote]

>>言葉遣いの問題もあり何を書いているのか理解できなかったです、すみません・・・

[quote="かずま" id=3,15604#postingbox,124127]
マーチさんのコードと、どこが違うのか説明してください。

親プロセスの totalTime を求める whileループは busy loop で、
CPU を使いすぎるので、よろしくありません。
usleep を使うことを検討してもいいのでは?
[/quote]

>>
・while (getchar() != '\n')によりバッファを初期化している。
・strcmpの文字数を決めている。
・フラグではなくwaitpidで子プロセスの状態変化を待っている。

busy loopやusleepは聞いたことのない単語です・・・調べてみます。


やはり子プロセスを破棄(殺す?)する方法がわからないです。
処理が終われば問題ないと思っていたのですがずっと残り続けるようで、exit関数でしょうか?
消したいのですが調べてもゾンビや養子などばかりで破棄の仕方がわからず・・・

あとendが入力された場合、全ての処理を終わらせたいのですが中々上手く行きません。
(最初に決めた時間を待たないですぐ処理を終わらせたい)
フラグで処理しようと考えていたときは

コード:

            while(1){
                endTime = clock();
                totalTime = ((endTime - startTime)*1000) / CLOCKS_PER_SEC;
                if(totalTime >= setTime) break; 
            }
のwhileの中と外にフラグを入れて対処しようとしていました。
そのため if (waitpid(p_id, &status, WNOHANG) == p_id) break;
を中と外に付けたのですが、またendを入れても終わらなくなってしまいました。
根本的にやり方が違うのでしょうか?

かずま

Re: 並行処理について

#6

投稿記事 by かずま » 11年前

マーチ さんが書きました:・while (getchar() != '\n')によりバッファを初期化している。
scanf の "%lf" は、数字しか読み込まないので、
数字の後に残っている '\n' までの文字をすべて
読み飛ばしています。
マーチ さんが書きました:・strcmpの文字数を決めている。
fgets で読み込むと '\n' がついているので、
"end" とマッチさせるために先頭 3文字だけを比較しています。
マーチ さんが書きました:・フラグではなくwaitpidで子プロセスの状態変化を待っている。
状態変化というのは、この場合、子プロセスの終了です。
マーチ さんが書きました:busy loopやusleepは聞いたことのない単語です・・・調べてみます。
busy loop とは、CPU が休む暇なく動いているループです。
ループを抜けるために何かを待っているので busy wait ともいいます。
今の場合、経過時間が setTime になるまでですが、そのため
今、何時? 今、何時? 今、何時? というのを何十万回、
何百万回と繰り返しているのです。
CPU の冷却ファンがうなり、熱風が出ませんか?
Linux なら、別の端末で top コマンドを実行して、
CPU の使用率を見てください。
Windows で Cygwin なら、タスクマネージャを起動して、
パフォーマンスを見てください。

sleep は秒単位、usleep はマイクロ秒単位で、時間経過を待つ関数です。
usleep を使ったプログラムです。

コード:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>
 
int main(void)
{
    pid_t p_id;
    int status;
    char message[256], buff[256];
    int msec, setTime;
    struct timeval tv0, tv;
 
    printf("\nメッセージは何にしますか?\n");
    if (!fgets(message, sizeof(message), stdin)) return 1;
 
    printf("何ミリ秒にセットしますか?\n");
    if (!fgets(buff, sizeof(buff), stdin)) return 1;
    setTime = atoi(buff);
    if (setTime == 0) return 1;

    p_id = fork();
    if (p_id == -1) return perror("fork"), 1;

    if (p_id == 0) { /* 子プロセス */
        printf("子プロセス開始\n");
        while (1) {
            printf("終了する場合は end を入力してください\n");
            if (!fgets(buff, sizeof(buff), stdin)) break;
            if (!strncmp(buff, "end", 3)) break;
        }
        printf("子プロセス終了\n");
    }
    else { /* 親プロセス */
        printf("親プロセス開始\n");
        gettimeofday(&tv0, NULL);
        do {
            gettimeofday(&tv, NULL);
            msec = (tv.tv_sec - tv0.tv_sec) * 1000
                   + (tv.tv_usec - tv0.tv_usec) / 1000;
            printf("%8d: %s", msec, message);
            usleep(setTime * 1000);
        } while (waitpid(p_id, &status, WNOHANG) == 0);
        printf("親プロセス終了\n");
    }
    return 0;
}
マーチ さんが書きました: やはり子プロセスを破棄(殺す?)する方法がわからないです。
処理が終われば問題ないと思っていたのですがずっと残り続けるようで、exit関数でしょうか?
消したいのですが調べてもゾンビや養子などばかりで破棄の仕方がわからず・・・
子プロセスは end の入力で while ループを抜け、main からリターンした後
exit で終了します。親プロセスの waitpid で成仏させればゾンビにはならないはずですが。
そのずっと走る続けるプログラムを貼ってください。
あと、環境はなんですか?

マーチ

Re: 並行処理について

#7

投稿記事 by マーチ » 11年前

続けていたら確かにうなりましたね・・・
ですがそれ以前に動いていないので書き換えはまた時間があるときにします・・・
今のプログラムは大して変わっていませんが

コード:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
 
int main(void)
{
    //プロセスID
    int pid;

    int status;

    char message[256];
    //時間 浮動小数点型がdouble
    double startTime, endTime;
    double totalTime = 0.0, setTime = 0.0;
 
    printf("メッセージは何にしますか?\n");
    fgets(message, sizeof(message), stdin);
 
    //時間をセット
    printf("何ミリ秒にセットしますか?\n");
    scanf("%lf", &setTime);
    while (getchar() != '\n') ;
 
    //子プロセスを起動する関数fork
    pid = fork();
    if (pid == -1) return 1;
    if (pid == 0) { /* 子プロセス */

        while (1) {
            char buff[256]; /* 入力文字列 */
            printf("終了する場合はexitを入力してください:");
            fgets(buff, sizeof(buff), stdin);
            if (!strncmp(buff, "end", 3)) break;
        }
        exit(0);

    }
    else { /* 親プロセス */
        while (1) {
     /* タイマー開始(ミリ秒単位) */
     //clockは経過時間
            startTime = clock();
            while (1) {
  //CLOCKS_PER_SECで割ると秒、ミリ秒なので1000倍

                endTime = clock();
                totalTime = ((endTime - startTime)*1000) / CLOCKS_PER_SEC;
                if (waitpid(pid, &status, WNOHANG) == pid) break;
                if (totalTime >= setTime) break;
            }
           
            //子プロセスが終了するのを待つ
            if (waitpid(pid, &status, WNOHANG) == pid) break;

            printf("\n%s", message);

        }
       
        wait(0);
       
    }
   
    return 0;
}

で環境はLinuxです。

今の問題点は
・子プロセスの成仏の仕方がわからない
・endが入力されてすぐプログラムを終えたいのに終えられない
この2点です。

次の課題がもう始まってしまい焦ってますが、
この課題を完成させたいのでどうかよろしくお願いいたします。

かずま

Re: 並行処理について

#8

投稿記事 by かずま » 11年前

子プロセスが生きている間、
waitpid(pid, &status, WNOHANG) は 0 を返し続けます。
子プロセスが死ぬと、
waitpid(pid, &status, WNOHANG) は pid を返します。
これで、子プロセスは成仏しました。
さらに、waitpid(pid, &status, WNOHANG) を呼び出し続けると、
pid のプロセスはないので -1 を返し続けます。
だから、終了しないのです。

一つ目の waitpid() を
if (waitpid(pid, &status, WNOHANG) == pid) return 0;
に書き換え、
二つ目の waitpid() と wait(0); を削除してください。

また、「... exit を入力してください」というメッセージは間違っています。

ところで、そのプログラムが動いている間、
他の端末で top コマンドを起動し、CPU使用率を見てみましたか?
こちらの質問にはできるだけこたえてください。

マーチ

Re: 並行処理について

#9

投稿記事 by マーチ » 11年前

連絡遅くなりすみません、返信ありがとうございます。

大変失礼しました。
調べたところCPU使用率は100%でした。
まさかこれほど負荷がかかっているとは思いませんでした。

今後は使用を控えようと思ったのですが、
既に次の課題がまた並列処理で今度は3秒という指定があったのでそのまま利用しています・・・
(同じ並列処理なのでこのままこのトピックを使おうと考えております)

マーチ

Re: 並行処理について

#10

投稿記事 by マーチ » 11年前

期限の切れている課題で少し急いでいるため、
続けて利用させていただきます、すみません。
以下のようなスレッドを用いた並列処理のプログラムを作成しております。

ルール
・スレッドはメッセージ表示、end入力、イベント送信の3つ
・入力したメッセージを3秒ごとに通常表示、逆から表示を交互にすること
・メッセージを表示させる関数を、関数ポインタとしてメッセージ表示のスレッドに渡すこと
・関数ポインタでメッセージ表示を交互に切り替えること

問題点
ルールの下2つを理解することができない。
・メッセージを表示させる関数とあるが、関数を作る必要があるのか。
この場合の作り方がわからない(そもそもメッセージの表示方法自体間違えている?)
・関数ポインタの使い方が調べて書いてみてもエラーばかりで理解できない
・関数ポインタをスレッドにどのように渡すか、どう交互に切り替えるのかわからない

初心者ですが、何卒ご指摘助言よろしくお願いいたします。
コードを書き込ませていただきます。

コード:

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

    int i, j=0;
    int mozisu;
    
    pthread_t th1, th2;
	
    char message[256];
    //時間 浮動小数点型がdouble
    double startTime, endTime;
    double totalTime = 0.0, setTime = 3.0;

// スレッド1
void* thread1( void* args )
{

    if(j % 2 == 0){
	//確認用
        //printf("endTime = %f\n", endTime);
        printf("\n%s", message);
    }
    else{
	//確認用
        //printf("endTime = %f\n", endTime);
        for(i=mozisu-1;i>=0;i--){
            printf("%c",message[i]);/*逆順に1文字ずつ出力*/
	}
	printf("\n");
    }
    j++;
 	
    return(0);
}

// スレッド2
void* thread2( void* args )
{

    while (1) {
    	char buff[256]; /* 入力文字列 */
        fgets(buff, sizeof(buff), stdin);
        //文字列を比較
        if (!strncmp(buff, "end", 3)) break;
    }
    exit(0);
    //pthread_join( th1, NULL );
    //pthread_join( th2, NULL );
}

// メインスレッド
int main(void) 
{

    printf("メッセージは何にしますか?\n");
    fgets(message, sizeof(message), stdin);
    
    mozisu=strlen(message);/*入力された文字数を取得*/

    printf("終了する場合はendを入力してください:");

    while (1) {
        /* タイマー開始(秒単位) */
        //clockは経過時間
        startTime = clock();
        while (1) {
	    //CLOCKS_PER_SECで割ると秒
            endTime = clock();
            totalTime = (endTime - startTime) / CLOCKS_PER_SEC;
            if (totalTime >= setTime) break;
        }

    	// スレッド1,2の作成と起動
        pthread_create( &th1, NULL, thread1, (void *)NULL );
	pthread_create( &th2, NULL, thread2, (void *)NULL );
	   
    }
	
    return 0;

}

sleep

Re: 並行処理について

#11

投稿記事 by sleep » 11年前

ざっと読んで、さっとC++でコードを作ってみましたが
むしろルールの方が曖昧で難しいですね。いまいち理解できていません。
少しでもお力になれればと思ったのですが、なれないかもしれません。(読解力の点で)

とりあえず、分かっている範囲で構いません。お時間の許す範囲で構わないので質問させてください。
答えられないものは答えていただかなくても大丈夫です。
マーチ さんが書きました: ・スレッドはメッセージ表示、end入力、イベント送信の3つ
・イベント送信の「イベント」は何をすることを指しているのでしょうか?
・3つのスレッドのうち、1つはプライマリスレッドが含まれていて良いでしょうか?
マーチ さんが書きました: ・入力したメッセージを3秒ごとに通常表示、逆から表示を交互にすること
・3秒のタイマ処理はsleepでも良いのでしょうか?
(settimerはプライマリスレッド以外でやろうとすると実権を乗っ取らないといけないのでそこそこ高いレベルのコーディングを要求されます)
マーチ さんが書きました: ・メッセージを表示させる関数を、関数ポインタとしてメッセージ表示のスレッドに渡すこと
・関数ポインタでメッセージ表示を交互に切り替えること
・関数ポインタで渡す関数の処理は、
 ①メッセージの表示
 ②メッセージの反転(制約:限定で関数ポインタで渡された関数内で行うこと)
 の2つの処理ができれば良いということでしょうか?


質問は以上です。
マーチさんの問題点については今の私では答えるのがまだ難しいですね。
申し訳ありません・・・

あと関数ポインタの使い方を載せておきます。

コード:

#include <stdio.h>

//関数ポインタ(関数の呼び出しアドレスをポインタとして格納できる型)
typedef void(*function_pointer)(char * message);

void display(char *message)
{
	printf("%s\n", message);
}

void func(function_pointer fp)
{
	fp("call func\n");
}

int main()
{
	//変数を宣言して使用
	function_pointer fp = display;
	fp("hello");

	//関数の引数の型として使用
	func(display);

	return 0;
}
マーチさんは C で作成されているみたいですね。
この段階でこちらのコードを張るのはいささか無礼なのですが、何ぶんルールに理解が追い付けなくて・・・
コンパイルと実行方法を載せておきますので、ルールがこういう動きで良いのか
できれば完成したときの動きだけ確認をお願いしたいのですがよろしいでしょうか?
(環境でもしコンパイル不可でしたら構いません)
もし、問題があれば読み飛ばしていただいて大丈夫です。

[環境]CentOS 6.5, gcc 4.9.0, binutils 2.24, openssl 1.0.1e-16.el6_5.15

コード:

//
// コンパイルとリンク方法
// g++ -std=c++0x -pthread -o test test.cpp
//
// 実行方法
// ./test
//

#include <iostream>
#include <string>
#include <algorithm>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;

//関数ポインタ
using function_pointer = void (*)(string &message);

string input_message();
void output_message(string &message);
void display_thread(mutex& Mutex, string &message, function_pointer pfunc);
void event_thread(mutex& Mutex);

int main()
{
  mutex Mutex;
  string message = input_message();

  //メッセージ表示スレッド起動(表示するメッセージと関数ポインタを渡す)
  thread(display_thread, ref(Mutex), ref(message), output_message).detach();
  //イベント送信スレッド起動
  thread(event_thread, ref(Mutex)).detach();

  //end入力
  while(1) {
    string str;
    cout << "終了するには \"end\" を入力してください" << endl;
    getline(cin, str);
    if(str == "end") {
      break;
    }
    Mutex.unlock();
  }
  return 0;
}

//メッセージ入力
string input_message() {
  string message;
  cout << "メッセージは何にしますか?" << endl;
  getline(cin, message);
  return move(message);
}

//メッセージ表示(関数ポインタとしてメッセージ表示スレッドに渡す)
void output_message(string &message){
  cout << message << endl;
  reverse(begin(message), end(message));
}

//メッセージ表示スレッド
void display_thread(mutex& Mutex, string &message, function_pointer pfunc)
{
  while(1) {
    Mutex.try_lock();
    Mutex.lock();
    //関数ポインタでメッセージを交互に反転させて表示
    pfunc(message);
  }
}

//イベント送信スレッド
void event_thread(mutex& Mutex)
{
  while(1){
    //3秒毎にメッセージ表示イベントを発生させる
    this_thread::sleep_for(chrono::milliseconds(3000));
    Mutex.unlock();
  }
}
長文失礼しました。

sleep

Re: 並行処理について

#12

投稿記事 by sleep » 11年前

すみません。
43行目のMutex.unlock();は、あっても大して支障はありませんが不要です。
削除していただいて大丈夫です。

コード:

    Mutex.unlock();

かずま

Re: 並行処理について

#13

投稿記事 by かずま » 11年前

エラー処理は省略しました。

コード:

#include <stdio.h>      // printf, puts, fgets
#include <string.h>     // strchar, strncmp
#include <unistd.h>     // sleep
#include <pthread.h>    // pthread_create, pthread_join, pthread_t
#include <semaphore.h>  // sem_init, sem_wait, sem_post, sem_destroy, sem_t
 
struct Data {
    int is_running;
    sem_t sem;
    char message[256];
    void (*print)(const char *msg);  // 関数へのポインタ
};

void *input_thread(void *arg)
{
    struct Data *dp = (struct Data *)arg;
    char buf[256];
    
    puts("input_thread starts");
    while (fgets(buf, sizeof buf, stdin)  && strncmp(buf, "end", 3))
        puts("enter end");
    dp->is_running = 0;
    puts("input_thread stops");
    return NULL;
}

void *output_thread(void *arg)
{
    struct Data *dp = (struct Data *)arg;

    puts("output_thread starts");
    do {
        dp->print(dp->message);
        sem_wait(&dp->sem);  // イベント待ち
    } while (dp->is_running);
    puts("output_thread stops");
    return NULL;
}

void print(const char *msg)
{
    puts(msg);
}

void reverse_print(const char *msg)
{
    int n = strlen(msg);
    while (--n >= 0) putchar(msg[n]);
    putchar('\n');
}
    
int main(void)
{
    struct Data data;
    pthread_t th1, th2;
    char *p;
    int i;

    printf("mesasge>> ");
    fgets(data.message, sizeof data.message, stdin);
    if (p = strchr(data.message, '\n')) *p = 0;

    data.print = reverse_print;
    data.is_running = 1;
    sem_init(&data.sem, 0, 0);
    pthread_create(&th1, NULL, input_thread, &data);
    pthread_create(&th2, NULL, output_thread, &data);

    while (data.is_running) {
        data.print = (data.print != print) ? print : reverse_print;
        for (i = 0; i < 3; i++) {
            sleep(1);
            if (!data.is_running) break;
        }
        sem_post(&data.sem); // イベント送信
    }

    pthread_join(th1, NULL);
    pthread_join(th2, NULL);
    sem_destroy(&data.sem);
    return 0;
}
理解できたら、説明をお願いします。
理解できなかったら、質問してください。

かずま

Re: 並行処理について

#14

投稿記事 by かずま » 11年前

やっぱり、3秒ごとにイベントを送信する処理を別スレッドにして、
mainスレッドは "end" 入力待ちにする方が自然かもしれません。

コード:

#include <stdio.h>      // printf, puts, fgets
#include <string.h>     // strchr, strncmp
#include <unistd.h>     // sleep
#include <pthread.h>    // pthread_create, pthread_join, pthread_t
#include <semaphore.h>  // sem_init, sem_wait, sem_post, sem_destroy, sem_t
 
struct Data {
    int is_running;
    sem_t sem;
    char message[256];
    void (*print)(const char *msg);  // 関数へのポインタ
};

void print(const char *msg)
{
    puts(msg);
}

void reverse_print(const char *msg)
{
    int n = strlen(msg);
    while (--n >= 0) putchar(msg[n]);
    putchar('\n');
}
    
void *timer_thread(void *arg)
{
    struct Data *dp = (struct Data *)arg;
    int i;

    puts("timer_thread starts");
    while (dp->is_running) {
        dp->print = (dp->print != print) ? print : reverse_print;
        for (i = 0; i < 3; i++) {  // 3秒待ち
            sleep(1);       // 1秒毎に終了チェック
            if (!dp->is_running) break;
        }
        sem_post(&dp->sem); // イベント送信
    }
    puts("timer_thread stops");
    return NULL;
}

void *output_thread(void *arg)
{
    struct Data *dp = (struct Data *)arg;

    puts("output_thread starts");
    do {
        dp->print(dp->message);
        sem_wait(&dp->sem);  // イベント待ち
    } while (dp->is_running);  // 終了チェック
    puts("output_thread stops");
    return NULL;
}

int main(void)
{
    struct Data data;
    pthread_t th1, th2;
    char buf[256], *p;

    printf("mesasge>> ");
    fgets(data.message, sizeof data.message, stdin);
    if (p = strchr(data.message, '\n')) *p = 0;

    data.print = reverse_print;
    data.is_running = 1;
    sem_init(&data.sem, 0, 0);
    pthread_create(&th1, NULL, timer_thread, &data);
    pthread_create(&th2, NULL, output_thread, &data);

    while (fgets(buf, sizeof buf, stdin) && strncmp(buf, "end", 3))
        puts("enter end");
    data.is_running = 0;

    pthread_join(th1, NULL);
    pthread_join(th2, NULL);
    sem_destroy(&data.sem);
    return 0;
}

sleep

Re: 並行処理について

#15

投稿記事 by sleep » 11年前

かずまさん、ありがとうございます。
目から鱗です。

ですが・・・当然マーチさん用ですよね。
私もかずまさんに教わって平気なんでしょうかね?(汗)
せっかくなのでとりあえず私も書かせていただきます。


※「そういう意味なのですね」と理解したところ
sleep さんが書きました: ・イベント送信の「イベント」は何をすることを指しているのでしょうか?
同期処理のことなのですね。
コードを書いていた時は半信半疑でしたが、偶然当たってたみたいですね。
確証まったくなかったですけど。
sleep さんが書きました: ・3つのスレッドのうち、1つはプライマリスレッドが含まれていて良いでしょうか?
プライマリスレッドも含むようですね。
sleep さんが書きました: ・3秒のタイマ処理はsleepでも良いのでしょうか?
使用して大丈夫そうですね。


※理解が苦しいところ
sleep さんが書きました: ・関数ポインタで渡す関数の処理は、
 ①メッセージの表示
 ②メッセージの反転(制約:限定で関数ポインタで渡された関数内で行うこと)
 の2つの処理ができれば良いということでしょうか?
マーチ さんが書きました: ・メッセージを表示させる関数を、関数ポインタとしてメッセージ表示のスレッドに渡すこと
・関数ポインタでメッセージ表示を交互に切り替えること
「メッセージ表示を交互に切り替える」とは書いてありますけど、「関数ポインタを交互に切り替える」とは書いていない気がしてます。
「関数ポインタとして」渡すと書いてありますから、「関数ポインタ=メッセージ表示用関数」で置き返ることができるからです。

・関数ポインタでメッセージ表示を交互に切り替えること

・メッセージ表示用関数でメッセージ表示を交互に切り替えること
と同意だと私には見えてしまいます。

むしろ、関数ポインタを切り替えるとは書いていない気がします。
かずまさんのコード上では、メッセージ表示用の関数ポインタを切り替えていました。
「メッセージ表示を行う関数ポインタを交互に切り替えること」なら分かるのですけど・・・
何か私はこの部分はすっきりしないですね。

以上、よろしくお願いします。

sleep

Re: 並行処理について

#16

投稿記事 by sleep » 11年前

いやー、しかしほんと難しい。日本語!
・・とはいえ、私が理解してマーチさんのお手伝いをできれば、と考えていたのですが
これはかずまさんが居れば全然大丈夫そうですね。
私では逆に日本語読解力の時点でマーチさんの足を只々ひっぱってしまいそうです(笑)

かずま

Re: 並行処理について

#17

投稿記事 by かずま » 11年前

セマフォの代わりに、ミューテックスを使うこともできます。

コード:

9c9
<     sem_t sem;
---
>     pthread_mutex_t mtx;
38c38
<         sem_post(&dp->sem); // イベント送信
---
>         pthread_mutex_unlock(&dp->mtx); // イベント送信
51c51
<         sem_wait(&dp->sem);  // イベント待ち
---
>         pthread_mutex_lock(&dp->mtx);  // イベント待ち
69c69
<     sem_init(&data.sem, 0, 0);
---
>     pthread_mutex_init(&data.mtx, 0); pthread_mutex_lock(&data.mtx);
79c79
<     sem_destroy(&data.sem);
---
>     pthread_mutex_destroy(&data.mtx);

マーチ

Re: 並行処理について

#18

投稿記事 by マーチ » 11年前

sleepさん

返信ありがとうございます!曖昧ですみません・・・
口頭で言われたのでもしかしたらニュアンスが違っていたかもしれません・・・

・イベント送信の「イベント」は何をすることを指しているのでしょうか?

>イベントは多分、時間のことだと思われます。3秒経過したというのがイベントのトリガー?だと考えております。

・3つのスレッドのうち、1つはプライマリスレッドが含まれていて良いでしょうか?

>メインスレッドのことでよろしいでしょうか?多分含まれていると思われます。

・3秒のタイマ処理はsleepでも良いのでしょうか?

>むしろその方が良いです。
かずまさんにも指摘されまして訂正したかったのですが、
急いでおり前回利用しても特に教授に何も言われなかったためそのまま利用していました。

・関数ポインタで渡す関数の処理は、
 ①メッセージの表示
 ②メッセージの反転(制約:限定で関数ポインタで渡された関数内で行うこと)
 の2つの処理ができれば良いということでしょうか?

>表示を行う関数をスレッドに渡すということなのでそうだと思われます。


関数ポインタの使い方が理解できました!ありがとうございます。



と、ここまで記載して寝落ちしてしまいました・・・
さらに説明不足でsleepさんやかずまさん御二方にご迷惑をおかけしてしまい申し訳ありません・・・。

お手伝いしたいというsleepさんの気持ち嬉しいです!ありがとうございます!
もしよろしければ次の課題も手伝っていただけると・・・
(次のネットワーク通信の知識は皆無で・・・)


かずまさん

毎度毎度本当にありがとうございます。構造体を利用するのですね。
どう説明すればいいか難しいので、ある程度コードにコメントとして記載いたしました。

ただ、セマフォはどのように説明すればいいのかまず理解できず全く分かりません・・・
セマフォはプロセス間の排他制御、ミューテックスはスレッドの排他制御?

コメントしたコードを載せます

コード:

#include <stdio.h>      // printf, puts, fgets
#include <string.h>     // strchr, strncmp
#include <unistd.h>     // sleep
#include <pthread.h>    // pthread_create, pthread_join, pthread_t
#include <semaphore.h>  // sem_init, sem_wait, sem_post, sem_destroy, sem_t
 
//構造体の宣言
struct Data {
    int is_running;
    //セマフォ宣言 処理を管理する
    sem_t sem;
    char message[256];
    // 関数へのポインタ
    void (*print)(const char *msg);  
};
 
//そのまま表示
void print(const char *msg)
{
    puts(msg);
}
 
//逆から表示
void reverse_print(const char *msg)
{
	//文字列長さ取得
    int n = strlen(msg);
    while (--n >= 0) putchar(msg[n]);
    putchar('\n');
}
    
//イベント処理(時間管理)スレッド
void *timer_thread(void *arg)
{
    struct Data *dp = (struct Data *)arg;
    int i;
 
    puts("timer_thread starts");
    //ポインタ演算子-> 左から右
    while (dp->is_running) {
	//参照しているのがprintではないときprint printならreverse_print
        dp->print = (dp->print != print) ? print : reverse_print;
	// 3秒待ち
        for (i = 0; i < 3; i++) {  
	    // 1秒毎に終了チェック
            sleep(1);       
            if (!dp->is_running) break;
        }
	// イベント送信 セマフォの加算
        sem_post(&dp->sem); 
    }
    puts("timer_thread stops");
    return NULL;
}
 
//表示スレッド
void *output_thread(void *arg)
{
    struct Data *dp = (struct Data *)arg;
 
    puts("output_thread starts");
    do {
	//メッセージ表示
        dp->print(dp->message);
        // イベント待ち セマフォでブロック
        sem_wait(&dp->sem);  
    // 終了チェック
    } while (dp->is_running);  
    puts("output_thread stops");
    return NULL;
}
 
int main(void)
{
    struct Data data;
    pthread_t th1, th2;
    char buf[256], *p;
 
    printf("mesasge>> ");
    fgets(data.message, sizeof data.message, stdin);
    if (p = strchr(data.message, '\n')) *p = 0;
 
    data.print = reverse_print;
    data.is_running = 1;
    //セマフォの初期化
    sem_init(&data.sem, 0, 0);
    //スレッド作成
    pthread_create(&th1, NULL, timer_thread, &data);
    pthread_create(&th2, NULL, output_thread, &data);
 
    while (fgets(buf, sizeof buf, stdin) && strncmp(buf, "end", 3))
        puts("enter end");
    data.is_running = 0;
 
    //スレッドの破棄
    pthread_join(th1, NULL);
    pthread_join(th2, NULL);
    //セマフォの削除
    sem_destroy(&data.sem);
    return 0;
}

ただ、また問題がありまして、教授にイベントって言ったのに何故セマフォなんだ?と言われてしまいました。
イベントの関数など調べてもなく、課題の詳細のパワポすら一切渡されない状態で全く答えがわからず・・・

イベントは状態を切り替えるためのトリガーであり、LINUXの何かの事象(事情?)が~と言っていたのですが、
一体イベントで処理しろとはどういうことなのでしょうか?
(それは違うよばかりではなくそろそろ授業をしてほしいものです・・・)

sleep

Re: 並行処理について

#19

投稿記事 by sleep » 11年前

マーチさん
返信ありがとうございます。
マーチさん さんが書きました: イベントは状態を切り替えるためのトリガーであり、LINUXの何かの事象(事情?)が~と言っていたのですが、
一体イベントで処理しろとはどういうことなのでしょうか?
あー、私とかずまさんは制御さえできれば大丈夫だと考えていたのですが
どうやら先生がおっしゃってるのは、おそらくシグナルのことかもしれませんね。

先生にずばり「イベント」の定義が何か伺ってみたほうがいいですね。
やはり仕様(ルール)は、人それぞれの知識の単語が混じるので確認が必要ですね。

私もかずまさんの考えを聞きたいので、偶に覗くことにします。

sleep

Re: 並行処理について

#20

投稿記事 by sleep » 11年前

そういえば、もしイベントの定義がシグナルだったときにどうすれば良いか
伝えておいた方がいいかもですね。

寝る前にスレッドでシグナルを受け取る方法を載せときます。
断りなくかずまさんのコードを貸していただく訳にもいかないので、
手抜きですが、私のC++コードにコメント付きで書いておきます。

言語の構造は C と変わらないので順序さえ合ってれば同じように使えます。
(私もCは普通に書けるんですが、書き直すのがめんどくさかっただけでごめんなさい・・・)
大ざっぱに言うと、変数の宣言位置のルールと呼び出してる関数が違うだけですね。
システムコールは一緒です。

説明用なので、正常系だけ読めれば良いと思ってエラー処理は省いてます。

コード:

#include <signal.h>
#include <unistd.h>

#include <iostream>
#include <string>
#include <algorithm>
#include <thread>
#include <chrono>
using namespace std;

//関数ポインタ
using function_pointer = void (*)(string& message);

string input_message();
void output_message(string& message);
void display_thread(string& message, function_pointer pfunc);
void event_thread();

int main()
{
  string message = input_message();

  //①スレッド作成前にsigprocmaskでプロセスにSIG_BLOCKを設定
  //-------------------------------------------
  sigset_t sigset;
  sigemptyset(&sigset);
  sigaddset(&sigset, SIGALRM);
  sigprocmask(SIG_BLOCK, &sigset, NULL);
  //-------------------------------------------

  //②スレッドはいつも通り普通に作成
  //-------------------------------------------
  //メッセージ表示スレッド起動(表示するメッセージと関数ポインタを渡す)
  thread(display_thread, ref(message), output_message).detach();
  //イベント送信スレッド起動
  thread(event_thread).detach();
  //-------------------------------------------

  //end入力
  while(1) {
    cout << "終了するには \"end\" を入力してください" << endl;
    string str;
    getline(cin, str);
    if(str == "end") break;
  }
  return 0;
}

//メッセージ入力
string input_message() {
  string message;
  cout << "メッセージは何にしますか?" << endl;
  getline(cin, message);
  return move(message);
}

//メッセージ表示(関数ポインタとしてメッセージ表示スレッドに渡す)
void output_message(string &message){
  cout << message << endl;
  reverse(begin(message), end(message));
}

//メッセージ表示スレッド
void display_thread(string& message, function_pointer pfunc)
{
  //③pthread_sigmaskで受信スレッドにSIG_BLOCKを設定
  //-------------------------------------------
  sigset_t sigset;
  sigemptyset(&sigset);
  sigaddset(&sigset, SIGALRM);
  pthread_sigmask(SIG_BLOCK, &sigset, NULL);
  //-------------------------------------------

  //⑤sigwaitで設定したシグナルを受信
  //-------------------------------------------
  while(1) {
    int sig;
    if(sigwait(&sigset, &sig) == 0) {
      switch(sig) {
      case SIGALRM:
        pfunc(message);
        break;
      }
    }
  }
  //-------------------------------------------
}

//イベント送信スレッド
void event_thread()
{
  while(1){
    //3秒毎にメッセージ表示イベントを発生させる
    this_thread::sleep_for(chrono::milliseconds(3000));

    //④killで(※重要)プロセス宛へシグナルを送信
    //-------------------------------------------
    kill(getpid(), SIGALRM);
    //-------------------------------------------
  }
}

sleep

Re: 並行処理について

#21

投稿記事 by sleep » 11年前

あと、スレッドに直接シグナルを送る pthread_kill というのもあります。

ただ、pthread_kill は スレッドIDを渡す必要があるので
ちょっと使いづらいかな、と思って説明するのをやめときました。

マーチ

Re: 並行処理について

#22

投稿記事 by マーチ » 11年前

CとC++はやっぱり違うものなのですね。

イベントの定義など含め様々な質問をしたことがあるのですが、
ほとんどが聞くな調べろという返答ばかりで・・・

イベントは状態を切り替えるためのトリガーであり、
メッセージの受信、3秒経過、end入力、全てがイベントであるということだと思われます。

でもメッセージの受信、end入力はスレッドにしろということなので、
多分イベントのスレッドというのは時間経過などのことだと思われます。


sigprocmaskやSIG_BLOCKとはなんでしょうか?調べてもいまいちわからず・・・
やはりシグナルの原理、使い方が理解できないです・・・。

コード:

#include <stdio.h>      // printf, puts, fgets
#include <string.h>     // strchr, strncmp
#include <unistd.h>     // sleep
#include <pthread.h>    // pthread_create, pthread_join, pthread_t
#include <semaphore.h>  // sem_init, sem_wait, sem_post, sem_destroy, sem_t
#include <signal.h>
#include <errno.h>
 
//構造体の宣言
struct Data {
    int timer;
    
    char message[256];
    // 関数へのポインタ
    void (*print)(const char *msg);  
};
 
//そのまま表示
void normal_print(const char *msg)
{
    puts(msg);
}
 
//逆から表示
void reverse_reverse_print(const char *msg)
{
	//文字列長さ取得
    int n = strlen(msg);
    while (--n >= 0) putchar(msg[n]);
    putchar('\n');
}
    
//イベント処理(時間管理)スレッド
void *timer_thread(void *arg)
{
    struct Data *dp = (struct Data *)arg;
    int i;
 
    puts("timer_thread starts");
    //ポインタ演算子-> 左から右
    while (dp->timer) {
	//参照しているのがnormalではないときnormal normalならreverse
        pp->print = (pp->print != normal_print) ? normal_print : reverse_print;
	// 3秒待ち
        for (i = 0; i < 3; i++) {  
	    // 1秒毎に終了チェック
            sleep(1);       
            if (!dp->timer) break;
        }

	//-----イベントを送信したい-----
	int raise( int sig );

    }
    puts("timer_thread stops");
    return NULL;
}
 
//表示スレッド
void *output_thread(void *arg)
{
    struct Data *dp = (struct Data *)arg;
 
    puts("output_thread starts");
    do {
	//メッセージ表示
        dp->print(dp->message);

	//-----イベントを受信するまで待ちたい-----
        int pause(void);   

    // 終了チェック
    } while (dp->is_running);  
    puts("output_thread stops");
    return NULL;
}
 
int main(void)
{
    struct Data data;
    pthread_t th1, th2;
    char buf[256], *p;
 
    printf("メッセージは何にしますか?");
    fgets(data.message, sizeof data.message, stdin);
    if (p = strchr(data.message, '\n')) *p = 0;
    printf("終了する場合はendを入力してください\n");
 
    data.print = reverse_print;
    data.is_running = 1;
    
    //スレッド作成
    pthread_create(&th1, NULL, timer_thread, &data);
    pthread_create(&th2, NULL, output_thread, &data);
 
    /終了処理
    while (fgets(buf, sizeof buf, stdin) && strncmp(buf, "end", 3))
    printf("終了する場合はexitを入力してください\n");
    data.timer = 0;
 
    //スレッドの破棄
    pthread_join(th1, NULL);
    pthread_join(th2, NULL);
    
    return 0;
}

マーチ

Re: 並行処理について

#23

投稿記事 by マーチ » 11年前

pause();でシグナルを受け取るまで、止めることができるのは分かったのですが、
どうここにシグナルを送るのかがわかりません・・・。
どう書けば送れるのでしょうか?

コード:

#include <stdio.h>      // printf, puts, fgets
#include <string.h>     // strchr, strncmp
#include <unistd.h>     // sleep
#include <pthread.h>    // pthread_create, pthread_join, pthread_t
#include <semaphore.h>  // sem_init, sem_wait, sem_post, sem_destroy, sem_t
#include <signal.h>
#include <errno.h>

//構造体の宣言
struct Data {
    int timer;

	char message[256];
	// 関数へのポインタ
	void (*print)(const char *msg);  
};

//そのまま表示
void normal_print(const char *msg)
{
	puts(msg);
}

//逆から表示
void reverse_print(const char *msg)
{
	//文字列長さ取得
	int n = strlen(msg);
	while (--n >= 0) putchar(msg[n]);
	putchar('\n');
}

//イベント処理(時間管理)スレッド
void *timer_thread(void *arg)
{
	struct Data *pp = (struct Data *)arg;
	int i;

	puts("timer_thread starts");
	//ポインタ演算子-> 左から右
	while (pp->timer) {
		//参照しているのがnormalではないときnormal normalならreverse
		pp->print = (pp->print != normal_print) ? normal_print : reverse_print;
		// 3秒待ち
		for (i = 0; i < 3; i++) {  
			// 1秒毎に終了チェック
			sleep(1);       
			if (!pp->timer) break;
		}

		//-----イベントを送信したい-----
		int raise( int sig );

	}
	puts("timer_thread stops");
	return NULL;
}

//表示スレッド
void *output_thread(void *arg)
{
	struct Data *pp = (struct Data *)arg;

	puts("output_thread starts");
	do {
		//メッセージ表示
		pp->print(pp->message);

		//-----イベントを受信するまで待ちたい-----
		pause();   

		// 終了チェック
	} while (pp->timer);  
	puts("output_thread stops");
	return NULL;
}

int main(void)
{
	struct Data data;
	pthread_t th1, th2;
	char buf[256], *p;

	printf("メッセージは何にしますか?");
	fgets(data.message, sizeof data.message, stdin);
	if (p = strchr(data.message, '\n')) *p = 0;
	printf("終了する場合はendを入力してください\n");

	data.print = reverse_print;
	data.timer = 1;

	//スレッド作成
	pthread_create(&th1, NULL, timer_thread, &data);
	pthread_create(&th2, NULL, output_thread, &data);

	//終了処理
	while (fgets(buf, sizeof buf, stdin) && strncmp(buf, "end", 3))
		printf("終了する場合はendを入力してください\n");
	data.timer = 0;

	//スレッドの破棄
	pthread_join(th1, NULL);
	pthread_join(th2, NULL);

	return 0;
}

閉鎖

“C言語何でも質問掲示板” へ戻る