後出しジャンケン日記

 この日の分のポストは、2009/10/23にアップしました。

要求定義

Intel Atomのようなトランジスタ数を劇的に減らした製品開発のために、CPUの設計を根本から見直している。省電力化はCPUにとって課題であり続けるが、従来製品と比較して処理能力的に劣っている製品を製造するのに、最先端の製造プロセスに投資をして利用するという意思決定をしているのだ。

 このようにして既に行われた意思決定を振り返って論じるのは簡単だが、現在進行中の状況であれば、その意思決定は難しくなる。他企業であれば、同様の状況下では従来通りの路線で処理能力が優先され、省電力化については「それも大事だ」と言われながらも優先度は二の次になるだろう。そして処理能力が劣る製品の製造には、「もったいない」と最新ではなく従来の製造プロセス(製造設備)を利用するというような意思決定になってしまうだろう。

 これって、「性能」というか、「品質」の定義の違いではないかな。
 つまり、狭義の「性能」をたとえばスループットの部分に限定しているとか。

スレッドプール

 使えそうかなー、使ってみたいなー、ということで、調査してみた。

 デリゲートしてやらせている処理など、バックグラウンドでスレッドプールが使用中の場合がある。スレッドプールで処理をパラレル化することで得られるはずのメリットが、期待通りのものではないことも考えられる。
 さらに、スレッドプールに任せる処理についても、通常のマルチスレッド同様のデッドロック対策が必要となる点も注意*1

スレッドプール関連のサンプル、その1

 とりあえず、NT5でも動作する、QueueUserWorkItem() APIを使って、スレッドプールっぽいことを試してみた。
 新APIが動作する環境がちょっと手許にないので、そっちは後回し。

/**
 * \brief QueueUserWorkItem()に登録されるタスク。
 */
DWORD WINAPI TPFunc1(LPVOID dummy)
{
	printf("TID:%d, Execute <%s>.\n\0",::GetCurrentThreadId(),"TPFunc1\0");
	return 0;
}

/**
 * \brief QueueUserWorkItem()に登録されるタスク。
 */
DWORD WINAPI TPFunc2(LPVOID dummy)
{
	printf("TID:%d, Execute <%s>.\n\0",::GetCurrentThreadId(),"TPFunc2\0");
	return 0;
}

/**
 * \brief QueueUserWorkItem()に登録されるタスク。
 */
DWORD WINAPI TPFunc3(LPVOID dummy)
{
	printf("TID:%d, Execute <%s>.\n\0",::GetCurrentThreadId(),"TPFunc3\0");
	return 0;
}

/**
 * \brief QueueUserWorkItem()に登録されるタスク。自身の中でさらにQueueUserWorkItem()を呼び出す。
 */
DWORD WINAPI TPFunc4a(LPVOID dummy)
{
	printf("TID:%d, Execute <%s>.\n\0",::GetCurrentThreadId(),"TPFunc4a\0");
	QueueUserWorkItem(TPFunc4b,0,WT_EXECUTEDEFAULT);
	return 0;
}

/**
 * \brief QueueUserWorkItem()に登録されるタスク。自身の中でさらにQueueUserWorkItem()を呼び出す。
 */
DWORD WINAPI TPFunc5a(LPVOID dummy)
{
	printf("TID:%d, Execute <%s>.\n\0",::GetCurrentThreadId(),"TPFunc5a\0");
	QueueUserWorkItem(TPFunc5b,0,WT_EXECUTEDEFAULT);
	return 0;
}

/**
 * \brief QueueUserWorkItem()に登録されるタスク。自身の中でさらにQueueUserWorkItem()を呼び出す。
 */
DWORD WINAPI TPFunc6a(LPVOID dummy)
{
	printf("TID:%d, Execute <%s>.\n\0",::GetCurrentThreadId(),"TPFunc6a\0");
	QueueUserWorkItem(TPFunc6b,0,WT_EXECUTEDEFAULT);
	return 0;
}

/**
 * \brief QueueUserWorkItem()に登録されるタスク。QueueUserWorkItem()に登録されたタスクによってキューイングされる。
 */
DWORD WINAPI TPFunc4b(LPVOID dummy)
{
	printf("TID:%d, Execute <%s>.\n\0",::GetCurrentThreadId(),"TPFunc4b\0");
	return 0;
}

/**
 * \brief QueueUserWorkItem()に登録されるタスク。QueueUserWorkItem()に登録されたタスクによってキューイングされる。
 */
DWORD WINAPI TPFunc5b(LPVOID dummy)
{
	printf("TID:%d, Execute <%s>.\n\0",::GetCurrentThreadId(),"TPFunc5b\0");
	return 0;
}

/**
 * \brief QueueUserWorkItem()に登録されるタスク。QueueUserWorkItem()に登録されたタスクによってキューイングされる。
 */
DWORD WINAPI TPFunc6b(LPVOID dummy)
{
	printf("TID:%d, Execute <%s>.\n\0",::GetCurrentThreadId(),"TPFunc6b\0");
	return 0;
}

void Task_Type1()
{
	for(UINT idx=0;idx<10;idx++){
		QueueUserWorkItem(TPFunc1,0,WT_EXECUTEDEFAULT);
		QueueUserWorkItem(TPFunc2,0,WT_EXECUTEDEFAULT);
		QueueUserWorkItem(TPFunc3,0,WT_EXECUTEDEFAULT);
	}
}

void Task_Type2()
{
	for(UINT idx=0;idx<10;idx++){
		QueueUserWorkItem(TPFunc4a,0,WT_EXECUTEDEFAULT);
		QueueUserWorkItem(TPFunc5a,0,WT_EXECUTEDEFAULT);
		QueueUserWorkItem(TPFunc6a,0,WT_EXECUTEDEFAULT);
	}
}

void Task_Type3()
{
	for(UINT idx=0;idx<10;idx++){
		QueueUserWorkItem(TPFunc4a,0,WT_EXECUTEDEFAULT);
		QueueUserWorkItem(TPFunc5a,0,WT_EXECUTEDEFAULT);
		QueueUserWorkItem(TPFunc6a,0,WT_EXECUTEDEFAULT);
		Sleep(100);
	}
}

int main(int argc, char* argv[])
{
	int type=0;

	printf("TID:%d, Execute <%s>.\n\0",::GetCurrentThreadId(),"main\0");
	if(argc>1)
	{
		if( strnicmp(argv[1],"/T:1\0",4)==0	)
		{
			type=1;
		}else if( strnicmp(argv[1],"/T:2\0",4)==0	)
		{
			type=2;
		}else if( strnicmp(argv[1],"/T:3\0",4)==0	)
		{
			type=3;
		}
	}

	switch(type)
	{
		case 1:
			Task_Type1();
			break;
		case 2:
			Task_Type2();
			break;
		case 3:
			Task_Type3();
			break;
		default:
			break;
	};
	Sleep(500);
	return 0;
}

 上記コードを実行するときは、コマンドライン

  • /t:1
  • /t:2
    • キューされたタスクが、自分自身で新たなタスクをキューに追加する
    • ぼくの環境で軽く動かしてみたところ、main()から登録したタスクが先にバーッと実行されたあとに、キューのタスクから登録したタスクが流れるような動き方をしていた。
  • /t:3
    • t:2の場合に対して、さらにmain()からのキュー登録タイミングをずらす

 いずれかのオプションが必要。
 結論としては、PostMessage APIに近い動きをすると考えればよさそう。
 但し、スレッドプールは明示的なスレッドのTermができないので、排他制御なんかを入れだすとデッドロックのリスクなどいろいろ問題が出てくる模様。
 要するに、キューされたタスクについては処理結果は見られないと考えたほうがいいだろう、という話*2
 ほか、新旧のAPIの違いとして、新APIではプールするスレッドの本数が変更できたり、システムイベントやタイマーをトリガーにしてタスクを起動したりする部分が追加されている模様*3

*1:とくに、Wait系で待機・常駐させる処理を割り当てている場合など。

*2:技術的に不可能なのではなく、リスクがある、という話

*3:ちょっと思ったのは、Windowsメッセージを処理するスレッドをプールできないのか、ということだったりする。APIを使ってWindowハンドルを生成するのはやっぱりメンドクサイし。