ウィリアムのいたずらの、まちあるき、たべあるき

ウィリアムのいたずらが、街歩き、食べ物、音楽等の個人的見解を主に書くブログです(たま~にコンピューター関係も)

Javaの並列処理:スレッド

2006-10-06 17:07:14 | JavaとWeb

 むかし、Javaの入出力:Socket通信(サーバー側 その3:複数クライアント対応プログラム)。 で、スレッドの話を書き、1通りのほうしか説明しなかったので、もう1つのほうも説明しておきます。




■スレッドの方法
 Javaでスレッド処理するには、上記のところにかいたように、

・Threadクラスを継承するものと
・Runnableインターフェースを実装するもの

があります。

 Threadクラスを実装するものについては、上記のところのとおりです。
  Runnableインターフェースを実装する方法について、今回は書きます。
 作り方は、ここを参照してください。



■ソース

やることは、
1.クラスにimplements Runnableとする
2.スレッドの発生は、

  test test1 = new test();
  Thread tr1 = new Thread(test1);
  tr1.start();

  のように、1のクラスをnewで生成し、それをThredクラスのコンストラクタの引数として
  Threadクラスを生成し、生成したものをstartさせます。
3.それ以外のrunメソッドの中身は、Threadクラスと同じです
  →今回はコピーしてきました。

 その結果、できたソースは以下のとおり
import java.io.*;
import java.net.*;

public class test implements Runnable {

	Socket socket = null;

	/*
	 * 	メイン処理(呼び出し元)
	 */
	public static void main(String[] args) 
	{
		try
		{
			// サーバーを生成
			ServerSocket serverSocket = new ServerSocket(5050);
			while(true)
			{			
				// ソケットを生成
				Socket socket = serverSocket.accept();

				//	スレッド生成
				test test1 = new test();
				test1.setSocket(socket);
				Thread tr1 = new Thread(test1);
				tr1.start();
			}	

		}
		catch(Exception e)
		{
			e.printStackTrace();
		}	
	}

 	
  	public void setSocket(Socket socket)
  	{
  		this.socket	=	socket;
  	}
  	
	/*
	 * 処理部分
	 */
	public void run()
	{
		if ( socket	==	null )
		{
			return;
		}

		try
		{
			//--------------------
			//受信する
			//--------------------
			InputStream	is1	
				=socket.getInputStream();
			InputStreamReader	ir1
				= new InputStreamReader(is1);
			BufferedReader		br1	= new BufferedReader(ir1);

			//	よみこめるまでまってる
			while(is1.available()	==	0);
		
			
			//	1行読み込む
			int	c;
			String  line = "";
			while((c = br1.read()) != '¥n' )
			{
				line = line + (char)c;
			}
				
			//--------------------
			//ファイル名のところを取り出す
			//--------------------
			String[] cell = line.split(" ");

			//	省略時、ファイル名を付ける
			if ( cell[1].charAt(cell[1].length()-1) == '/' )
			{
				cell[1] = cell[1] + "index.htm";
			}

			//	ファイル名を作成
			String fname = "";
			fname = cell[1].substring(1);
			System.out.println(fname);

			//--------------------
			//ファイルを取得
			//--------------------
			File	f = new File(fname);
			FileInputStream fi = new FileInputStream(f);
			byte[] data = new byte[(int)f.length()];
			fi.read(data);
			fi.close();
			String dataStr = new String(data);
			
			//--------------------
			//送信する
			//--------------------
			OutputStreamWriter	ow1
				= new OutputStreamWriter(socket.getOutputStream());
			BufferedWriter bw1 = new BufferedWriter(ow1);
			
			bw1.write(dataStr);
			bw1.flush();
			
			
			//	クローズ
			bw1.close();
			ow1.close();
			br1.close();
			ir1.close();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
	}
}

(上記< > ¥は、本当は半角です)

これを起動して、
ブラウザで、
http://127.0.0.1:5050/test.java
とか指定すると、起動したところのローカルにある、
test.javaファイルの内容をブラウザに返します。


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

ソフトのプロジェクトの予実管理における差異分析と、製造業の差異分析の違い

2006-10-06 15:20:22 | Weblog

 今回は、初めのほうはプロジェクト管理の話題なんだけど、一番最後に、さっき書いた画面ごとになんで分けることにこだわるのかについて書きます。




 予定と実績を管理する(予実管理)際、製造業だと、操業度差異とか、予算差異、能率差異にわけたりしますよね。ま、これを標準原価管理っていうわけだけど。。これによって、予算との狂いは、どこが問題なのかっていうのを調べる。

 で、ソフトウエアのプロジェクト管理で、この差異分析をしたい場合、アーンドバリュー分析を行う。これは、

1.予算をだし
2.出来高を出し
3.実際にかかったお金をだす。

 ただ、このやり方だと、ソフト開発の場合、問題だ。
 っていうのは、何百時間残業しても、お金は変わらないというケースがあるからだ(請負の場合、サービス残業の場合)。でも、だからといって、何時間も残業させて、スケジュールOKっていうわけじゃない。
 そこで、アーンドバリュー分析の場合、すべてお金で換算するけど、時間でやってしまうという変形を加えたほうがいいことが多い。
 これが、確か、数年前、情報処理試験のプロジェクトマネージャーの午後1に出ていた問題だと思うけど、こういうふうにもとめる。

1.機能をわけ、その機能にかかる時間を出し、これを予定とする(PV)
2.機能が開発完了した場合、
    完了した機能数X(1のときにその機能にかかるとした予定時間)
  の合計をもとめ、出来高とする(EV)
3.実際の労務時間をもとめる(AC)

 こうしたとき、

S:予定と、実際の機能の完了との差(EV-PV)が、スケジュール差異で、
C:できた機能からみて、本来かかるはずの時間(2)と実際の労務時間の差(EV-AC)がコスト差異

 となる。

 本当のアーンドバリューはお金なので、上記のものとはちがうんだけど、こうやったほうが、管理しやすい。また、時間は、何人日単位で、小数点以下をみとめる。つまり、1日で、残業4時間なら、1.5日(8時間労働とする)、1日で残業8時間なら、それは、2日と計算する。逆に、4時間しかかかんなかったら0.5日とする。




 で、こうなったとき、4つの可能性がある
 (なお、Sはスケジュール差異、Cは、コスト差異)

(あ)SもCも、だめだめ、
  つまり、スケジュールも遅れてるし、そのわりに時間もかかりすぎている
  ただし、実際には、途中で退社することはできないので、単純に手待ちのときでも、
  このような現象が起きる(なにもすることなく、無駄な時間が流れてる)

(い)SはOK,Cだめだめ、 
  スケジュールはあってるんだけど、みんな死ぬほど残業して、どうにか間に合わせている
  (別に死ぬほどでなくてもいいんだけど)

(う)Sはだめだめ,CはOK
  機能の遅れはあるんだけど、その機能に対するかかっている時間は、問題ない。
    =>手待ちになっている可能性がある
  ただ、手待ちは、(あ)や(い)の場合でもなり、
  実際には(あ)のカタチで出ることが多い。

(え)SもCもOK
 機能も遅れてないし、順調=>あんまりない。




 で、ここまでは、情報処理試験で出ているくらいだから、まあ、みんな知っているのだろう。
 問題はこの先。

 このとき、予定を出すのに、製造業であれば、「標準原価カード」があるから、それを使えばいい。でも、ソフト開発の場合、それに相当するものを作らないといけない。

 そこで、機能をわけ、その機能ごとの標準時間を求めないといけないことになる。

 そのとき、機能をどう分けるか。。。

 というと、標準時間が求まるほど、分かりきった単位。。

 ってなると、画面とか、帳票とかで分けるしかない。。




 さっきのブログで、「なぜ、画面にこだわるか」といったのは、この理由のため。
 画面ごとでないと、予実の管理がたいへん。

 で、画面の作り方を決めてしまえば、この部分の標準時間は出る。
 ついでにファイル入出力などもやり方決めてしまえば、標準時間は出るので、

 あとは、カメラ部分とか、通信部分とかを、要素技術として切り出し、ここを別に計画を立てれば、業務アプリ開発っぽく管理できる。

 なので、画面わけて開発できること、大事!
 

  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

先ほどのBREWで分散環境のまとめの話の追加

2006-10-06 14:16:31 | ケータイ

 先ほど書いたのはなし(ここ)で、書き忘れたことがあったので、ちょっと追加します。

 サーバーに置くのは、version.hの本番用です。(つまり、デバッグ情報のないもの)

 で、各自のローカルに、デバッグ用のversion.hをおきます。これは、サーバーにアップしません。そして、gamen2用のテストに、version2.h,gamen3用のテストに、version3.h。。。などと、いろいろもっていて、テストするときに、version2.hをversion.hにコピーして、コンパイル、テストします。

 なので、見本に書いているversion.hは、画面2のデバッグ用と、本番用の2つ書いてありますけど、これはあくまでも、サンプルで、実際には、サーバーには

#define START_FUNC gamen10_InitAppData(pMe)
#define DEBUG_FUNC


 ローカルには、

#define _DEBUG_GAMEN2
#define START_FUNC gamen2_InitAppData(pMe)
#define DEBUG_FUNC gamen2_debug(pMe)


というようになります。

 ただし、gamen2.c,gamen2.hは、デバッグのプログラムがはいったまま、サーバーにアップします。そのようにしても、デバッグ部分は、#ifdef _DEBUG_GAMEN2のように、囲まれているので、本番用コンパイルの時には、ここは読み飛ばされ、ないものとされるので、問題ありません。

 なお、画面のソースにデバッグのソースを持ちたくない場合(デバッグのソースを分けたい場合=わけると、テスト用プログラムを別の人が先行して作成できたり、値設定をファイルから読み込んだりして共通化できる)については、ずっと先に書くかも?しれません。




それよりも、画面ごとになんで分けることにこだわるのかについて、今度書きたいと思います。

  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

BREWで複数画面を(分割して開発可能な)開発する場合の方法論(その9:分散開発の問題と解決策)。

2006-10-06 13:03:55 | ケータイ

 シリーズ「BREWで複数画面を(分割して開発可能な)開発する場合の方法論」のつづきです。
 前回で、一通りできました。。めでたしめでたし。。。

 ではなくって、分散開発上(構成管理の上で)、致命的な問題があります。
 今回は、その問題と解決法を書き、とりあえず、ここのまとめをしたあとで、さらに解決策の発展としたカオル姫方式の予告をしたいと思っています。




■分散開発の際の、構成管理上の致命的問題点

 今回は、画面ことに開発するということでした。ということは、gamen2のテストとgamen10のテストは、別会社で、連絡も無いところがやっているかもしれません。それも同時に。

 この場合、
・gamen2作成会社は、デバック_DEBUG_GAMEN2のところしかいれていません。
・gamen10作成会社は、デバック_DEBUG_GAMEN10のところしかいれていません。
・さらに、全体アプリはfukusu1を、画面が増えるたびに(HandleEvent追加で)修正します。

 この状況で、
(1)gamen2の会社は、_DEBUG_GAMEN2を、gamen10作成会社は、デバック_DEBUG_GAMEN10を
   いれてテストします
(2)このときデバッグオプションのはいったfukusu1は、サーバーにアップしないとすると
   →毎回、画面が増えてfukusu1が追加されるたびに、デバッグするときに、デバッグ
    箇所を追加しないといけません(古いfukusu1を使うと、新しい画面が起動しない)

かといって
(2)’このときデバッグオプションのはいったfukusu1を、サーバーにアップしてしまうと
(3)gamen2の人がまず、アップし
(4)そのあと、gamen10の人が、単純にアップしてしまうと、
   gamen2のデバッグ情報が消えてしまいます。
   なので、このfukusu1に画面が増えて追加されたものを、デバッグしようとしたら
   結局、デバッグ箇所を追加しないといけません。。
(4)’じゃあ、gamen10の人は、まずサーバーからダウンロードし、そのあとで、
   gamen10の人が、そのソースにgamen10のデバッグ情報を追加すれば。。。
   といったら、その追加する分手間っていうこともあるけど、このとき、
   gamen1の人も同時に追加作業をやっていたりすると、そのあと、どっちがコミット
   するかにより、gamen1かgamen10のどちらかの情報が消えます。
   だから、だめ

となってしまい、どっちにしろ、デバックするたびに、その部分のソースをいれないといけなくなります。作業増えます。だめだめです(>_<!)

 っていうか、これだと、1ファイル1担当者の原則に反します。




■解決策の前に、どうすればいいのか?

1ファイル1担当者なので、

・全体アプリの担当者がHandleEventと共通部分を追加して、アップしたら、あとは、
 このソースはだれもいじらない。

・スタートの関数と、デバッグ用の関数の設定は、ローカルテスト用と、全体リリース用で
 切り替えられるようにする(全体リリースにローカルの関数が入らない)

とする必要があります。




■解決策 関数名をバージョンのヘッダに書き、マクロを使う

 これは、こうすると、解決します。
 なお、以前、ここで、「画面数がふえると、たまったもんでないのですが、じつは、こうならない方法があります。」と書いた方法です。

●fukusu1.cの、デバッグのところ、スタートを呼ぶ関数のところを、マクロにします。
 fukusu1.cのデバッグを呼ぶところは、以下のように、DEBUG_FUNCにしています。
boolean fukusu1_InitAppData(fukusu1* pMe)
{
    // Get the device information for this handset.
    // Reference all the data by looking at the pMe->DeviceInfo structure
    // Check the API reference guide for all the handy device info you can get
    pMe->DeviceInfo.wStructSize = sizeof(pMe->DeviceInfo);
    ISHELL_GetDeviceInfo(pMe->a.m_pIShell,&pMe->DeviceInfo);

    // The display and shell interfaces are always created by
    // default, so we'll asign them so that you can access
    // them via the standard "pMe->" without the "a."
    pMe->pIDisplay = pMe->a.m_pIDisplay;
    pMe->pIShell   = pMe->a.m_pIShell;

    // Insert your code here for initializing or allocating resources...
	pMe->gno	=	0;
	pMe->garea	=	NULL;
	MEMSET(pMe->sei_ritu,0,20);
	MEMSET(pMe->byo,0,20);
	MEMSET(pMe->username,0,21);

	//	デバッグ用設定
	DEBUG_FUNC;

    // if there have been no failures up to this point then return success
    return TRUE;
}

(上記< > ¥は、本当は半角です)

そして、HandleEventのところも、マクロにします。
static boolean fukusu1_HandleEvent(fukusu1* pMe,
   AEEEvent eCode, uint16 wParam, uint32 dwParam)
{  
	boolean	ret;

	ret	=	FALSE;	//	処理していない

		//	スタート時に、はじめの画面を呼ぶ
	if ( eCode	==	EVT_APP_START)
	{
		return	START_FUNC;
	}
	else if ( pMe	==	NULL )
	{
		return	FALSE;
	}


(上記< > ¥は、本当は半角です)

●画面2のデバッグの場合、version.hで、以下のように、宣言します。
#define	_DEBUG_GAMEN2
#define	START_FUNC	gamen2_InitAppData(pMe)
#define	DEBUG_FUNC	gamen2_debug(pMe)


●gamen2.hで、あたらしくつくった、デバッグ用関数gamen2_debug(pMe)を
#ifdef _DEBUG_GAMEN2
void gamen2_debug(fukusu1 *pMe);
#endif

のように宣言します(externのほうも、追加します)

●gamen2.cで、デバッグ用関数gamen2_debug(pMe)を記述します
#ifdef _DEBUG_GAMEN2
void gamen2_debug(fukusu1 *pMe)
{
	STRCPY(pMe->sei_ritu,"100");
	STRCPY(pMe->byo,"20");
	STRCPY(pMe->username,"ウィリアムのいたずら");
}
#endif

(上記< > ¥は、本当は半角です)

 gamen2の場合の修正は、これでOKです。こうすると、fukusu1のマクロが展開されて、実際には、version.hで宣言されている、それぞれの関数を見ます。




 gamen10の場合の修正は、gamen2をgamen10に変えて、同様のことをしてください。
 で、本番の場合は、version.hを、以下のように
#define	START_FUNC	gamen10_InitAppData(pMe)
#define	DEBUG_FUNC

としてしまえば、gamen10からはじまり、デバッグ処理はなにもしないで、できます。




 こうすると、デバックとスタートの部分は、マクロになっていて、文字ずらは変わらないので(実際の中身は、コンパイル時にマクロ展開されて変わるけど)fukusu1をgamen2の人もgamen10のひとも皆共有できます。そしてfukusu1の人は、関数がふえたり、共通部分が増えたりしたら、それを追加し、配布すればいいだけになります。




■まとめ
 ということで、このまとめとして、ソースは、以下のようになります。
   version.h
   fukusu1.c
   fukusu1.h
   gamen1.c
   gamen1.h
   gamen1.htm
   gamen2.c
   gamen2.h
   gamen2.htm
   gamen10.c
   gamen10.h
   IHtmlCtl.c
   IHtmlCtl.h

なお、fukusu1.mif,fukusu1.bidは、MIFエディタで作成したものを使います。




■解決策の発展
 しかし、もう一歩すすめて考えると、
 新規画面のとき、fukusu1を直してもらう、これはまあ、いいとして。。
 共通部分の変数って言うのは、画面間の打ち合わせで急に出てきたりします。
 それをすべて、管理してfukusu1に反映するっていうのは、大変です(@_@!)

 なので、ここでは、カオル姫方式、つまり、
・画面間にまたがるデータをハッシュマップにいれてしまう
・そのハッシュマップを全体、各画面で共有するようにする
・中身はテキトーに入れてね。。。
ってすれば、
・fukusu1では、そのハッシュマップを生成して、そのポインタを持っているだけでいい。
・あとは、各画面において、データを入れてくれたり、使ってくれたりすればいい。
・構造体にハッシュマップを持ってるだけだから、共通変数が追加されても、
 fukusu1のソースは変わらない

 ということが実現できます。。

 え、よくわかんない。。

 それを、このシリーズの次回から説明していきます。



  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする