処理されるステートメントの順番を強引にねじ曲げる究極の方法が存在する。ステートメントには「ラベル」というものを付けることができ、そのラベルに対して制御を移すgotoステートメントが存在する。強力すぎてたいへん危険な機能であり、利用することはあまりお勧めできない。だが、この機能を使ったほうがきれいで読みやすくなる場合もまれに存在するので、機能として用意されているものと思われる。まずは、List 9-8を見てほしい。
1: using System;
2:
3: namespace Sample008
4: {
5: class Class1
6: {
7: [STAThread]
8: static void Main(string[] args)
9: {
10: if( args.Length == 1 )
11: {
12: goto x;
13: }
14: if( args.Length == 2 )
15: {
16: goto y;
17: }
18: Console.WriteLine("without labels");
19: x: Console.WriteLine("with label x");
20: y: Console.WriteLine("with label y");
21: }
22: }
23: }
このサンプル・ソースを実行した結果はFig.9-8のようになる。
List 9-8では、コマンドライン引数が1の場合は、12行目から19行目に一気に処理が飛ぶ。2の場合は、16行目から20行目に飛ぶ。いずれでもない場合はそのまま処理が進み、18〜20行目の出力が行われてから終了する。
Column - スレッド切り替えをロックするlockステートメント -
本連載では主に言語仕様を扱うため、主にクラス・ライブラリ経由で機能が提供されるスレッドについては触れていない。しかし、言語仕様の中にもスレッドと関係するものがある。それがlockステートメントである。これはある参照型の値へのアクセスを特定のスレッドで独占するために使用することができる機能である。
例えば、以下のようなソースがあったとする。「HelloWorld」と「C#」という文字列を出力するスレッドが2本並行して動作する。
1: using System;
2: using System.Threading;
3:
4: namespace Sample009
5: {
6: class Class1
7: {
8: private void sample1()
9: {
10: for( int i=0; i<1000; i++ )
11: {
12: Console.Write("Hello");
13: Console.Write("World ");
14: }
15: }
16: private void sample2()
17: {
18: for( int i=0; i<1000; i++ )
19: {
20: Console.Write("C");
21: Console.Write("# ");
22: }
23: }
24: [STAThread]
25: static void Main(string[] args)
26: {
27: Class1 instance = new Class1();
28: Thread thread1 = new Thread( new ThreadStart(instance.sample1) );
29: Thread thread2 = new Thread( new ThreadStart(instance.sample2) );
30: thread1.Start();
31: thread2.Start();
32: }
33: }
34: }
これを実行すると以下のようになる。
HelloとWorld、Cと#はそれぞれ独立した出力呼び出しで出力されており、途中でスレッド切り替えが入ると容易にHelloとWorld、Cと#は泣き別れになってしまう。実際、出力画面の中に、Helloの次がWorldにならず#が入り込んでいる例があるのが分かると思う(編集部注:上図の赤下線部分)。このような割り込みを抑止して、必ずHelloとWorldを連続して出力するようにするには以下のようにソースを修正する。
1: using System;
2: using System.Threading;
3:
4: namespace Sample010
5: {
6: class Class1
7: {
8: private void sample1()
9: {
10: for( int i=0; i<1000; i++ )
11: {
12: lock( this )
13: {
14: Console.Write("Hello");
15: Console.Write("World ");
16: }
17: }
18: }
19: private void sample2()
20: {
21: for( int i=0; i<1000; i++ )
22: {
23: lock( this )
24: {
25: Console.Write("C");
26: Console.Write("# ");
27: }
28: }
29: }
30: [STAThread]
31: static void Main(string[] args)
32: {
33: Class1 instance = new Class1();
34: Thread thread1 = new Thread( new ThreadStart(instance.sample1) );
35: Thread thread2 = new Thread( new ThreadStart(instance.sample2) );
36: thread1.Start();
37: thread2.Start();
38: }
39: }
40: }
これを実行すると以下のようになる。
12〜16行目、23〜27行目のlockステートメントが、その内部の実行中にもう1つのスレッドが割り込んでこないようにスレッド切り替えをロックしてしまっている。しかし、すべてのスレッド切り替えをロックしているわけではなく、lockステートメントで指定された参照(ここではthis〈現在実行中のインスタンスを示すキーワード〉)をロックしようとするスレッドへの切り替えを抑止するのである。この結果、出力画面のように文字列の泣き別れがなくなったことが分かると思う。
Copyright© Digital Advantage Corp. All Rights Reserved.