の 0 ①セヲフロクラミング 行われないという状態になるだけです。と くに , main 関数は処理をするスレッドを起 動するだけという作り方をしたときは , mai n 関数の終了が , 即 , プロセスの終了にな るので , main 関数を終了する前に起動した スレッドが処理を終了するのを待つような 処理をする必要があります。 List14 でコメ #include く time. h> #include く stdio. h> ファイルシェアリング め , メッセージは表示されません。 がスレッドの処理終了を待つ処理で , これ がコメントアウトされていると foo 関数のス レッドを待たずに main 関数が終了するた ントアウトされている , while(cnt); ふたつのスレッドで , 同じファイルに書 き込むことになったとき , 意図したように 書き込めないことがあります。一方のスレ ッドでファイルを開いたままで時間のかか る処理をしながらファイルへの書き込みを しているとき , 別のスレッドが同じファイ すが , f001 関数の "Task:test1" と f002 関数の キストファイル test は Fig. 8 のようになりま たとえば , List 15 の実行後に作成されたテ 順不同で行われるという結果になります。 書き込みと 2 番目のスレッドの書き込みが ルに書き込もうとすると最初のスレッドの ふたつのスレッドによるファイルの同時書き込み #include く s し d 土 0 . h > #include <stdlib. h> #include <string. h> く e てて no . h> <process. h> volatile int cnt = の # incl ude #include ff lush(fp); fprintf(fp, "Task:%s%n",p); for(i=0 く 1 の i 十十 ) { return ー cnt— printf( "fopen error[name:%s] : printf( ”宅 s start*n" ,p); time—t t; int 土ー FILE *fp; void fool(void *p) 'p,strerror(errno) t = me ( / * 時間の掛かる処理の代わり void f002 ( VO 土 d *p) cnt— fclose(fp); while(t 十 2>time(NULL) FILE *fp; int time—t セー printf( ″宅 s start%n" ,p); if い ()p = fopen("test","a"))){ printf( "fopen error[name:%s] : cnt— return ー for(i=0; i く 1 の i 十十 ) { fprintf (fp, "Task:%s%nn ,p); fflush(fp); s 基 n ” ,p,strerror(errno) - fsopen 関数を使った排他制御 #include <stdlib. h> #include く string. ね> <errno . h> く process. h> <time . h> #include く share . h> vo ねし e int cnt #include #include #include 1 6 void f002 ( VO 土 d *p) cnt— fclose(fp); while(t 十 2>time(NULL) 潺 t = time(NULL); / * 時間の掛かる処理の代わり * / ff lush(fp); fprintf(fp, "Task:%s%n" ,p); fo て ( i = 土く 1 の土十十 ) { return ー cnt- printf( "fopen error[name:%s] : 宅 s 基 n ” ,p,strerror(errno) if( ! ()p = -fsopen( ntest" , ” a ″ ,SH-DENYWR) ) ) { printf("%s start%n",p); time—t t ー FILE *fp; void fool(void *p) し = time(NULL); / * 時間の掛かる処理の代わり * / fflush(fp); fprintf( fp, "Task:%s%nn,p); for ( i = i く 1 i 十十 ) { return ー cnt— printf( "fopen error[name:%s] : %s*n",p,strerror(errno) if い ()p = —fsopen( "test" , ” a ” ,SH-DENYWR) ) ) { printf( ″宅 s start%n" 'P); time—t セー int i ー FILE *fp; int t = time(NULL); / * 時間の掛かる処理の代わり * / while(t 十 2>time(NULL) cnt— fclose(fp); return 0 ー whi ( cn に if(ret = = -1 ) exit(l); ,ret); ret = ー begin セ耻 ead ( f002 , 1024 , ” tes し 2 ” cnt 十十一 ー 1 ) ex 辻 ( 1 if(ret printf( "ret=%d*n",ret); ret = —beginthread(f001,1024, ″ tes セ 1 ” cn し十十一 int て e セー main ( ) 土 n while(t 十 2>time(NULL) fclose(fp); cnt- main ( ) int て e セー cnt 十十一 ret = —beginthread(f001,1024,"test1" printf( ”て et = 宅 d n ″ ,ret); -1 ) exit(l); if(ret cn セ十十一 ret = —beginthread(f002,1024, "test2" printf( ″て et = 宅 d 基 n ” ,ret); if(ret = = -1 ) exit(l); while(cnt); return 0 ー 特集 1 セーフテイプログラミング 15
セヲフワクラミ スレッド間の競合 List 20 の例では , 共有リソースがひとつ だけでしたが , 当然 , 共有リソースがふた つ以上になることもあります。この場合 クリティカルセクションの設定が , デッド ロックを起こさないように注意しなければ なりません。 つまり , 一方のスレッドが , あるリソー スにクリティカルセクションを設定し , 別 のスレッドがもうひとつのリソースにクリ ティカルセクションを設定したとします。 クリティカルセクションを使わない場合 # incl ude く process. h> # incl ude くセ土 me. h> # incl ude く std lib. h> #include <stdio. h> #include く windows . h> volatile int cnt = 0 ー void f002 ( vo 土 d * void fool(void * ary [ 幻 ifCOUL = cnt 十十一 int 土ー int main ( ) int ary[20]; for(i = の土く 2 土十十 ) { / * データの初期化 * / = —beginthread(f001,1024,NULL) ) { pe てて 0 て ( "—beginthread e てて 0 て″ if COUL = = -beginthread(f002,1024,NULL) ) { cn し十十一 exit( 1 perror ( "—beginthread error ″ ex 址 ( 1 return 0 ー while(cnt); time—t t ー int void f001 ( void *p ) ary[ 幻 = 19 - for(i = の i く 1 i 十十 ) { cnt— fo て ( 土 = i く 2 i 十十 ) { void f002(void *p) ary[i] = 19 ー for(; 土く 2 土十十 ) ( t = time(NULL); そして , それらのクリティカルセクション を設定したまま , さらに , お互いに別のス レッドが使用しているリソースを使おうと , クリティカルセクションが空くのを待っと , 両方のスレッドが待ちの状態に入ってしま い , プログラムが止まってしまいます (Fig. 13 ) 。このようなテッドロックを起こすプロ グラムの例は List21 のようになります。 List 21 を実行すると , ふたつのスレッドはそれ ぞれ 2 番目の EnterCriticalSection 関数で止 まってしまいます。 このようなエラーを起こさないためには 同時にふたつのリソースを占有することが ないようにします。デッドロックを解消す るために , Ⅱ st21 のふたつの配列を同時に 占有しないように書き換えると List 22 のよ うになります。 List 22 では配列血 pa Ⅳを一 時的なデータ作成用の変数として , 配列 a Ⅳ 1 や a Ⅳ 2 から読み出したデータを入 れています。また , 書き込むデータの書き 込みも血 pa 配列上にあらかじめ構成して おいて , 一気に書き込むという方法をとる ことで , ふたつの配列の同時占有をしない ようにしています。 while(t 十 5 > time(NULL)); / * 他のスレッドが割り込む確率を高めるための cnt— printf( "uvn",ary[i] クリティカルセクションを使った場合 cn セ十十一 InitializeCriticalSection(&cs); ary は ] for(i = i く 2 の i 十十 ) { / * テータの初期化 * / int 土ー int main ( ) CRITICAL—SECTION cs; int a て y [ 20 volatile 土 n セ cnt void f002 ( vo 土 d * void f001 ( void * #include <process. h> #inc lude く time. h> #include <stdlib. h> #include く stdio. h> #include く windows . h> DeleteCriticalSection(&cs); while(cnt); exit(l); perror ( ”—beginthread error" ifCOUL = = -beginthread(f002,1024,NULL) ) { cnt 十十一 ex 辻 ( 1 pe てて 0 て ( n—beginthread error ” if COUL = = —beginthread(f001,1024,NULL) ) { cnt— LeaveCriticalSection(&cs); = 19 ー ary [ 幻 for(; 土く 2 i 十十 ) { t = time(NULL); = 19 - ary は ] for(i = 土く 1 i 十十 ) { EnterCritical Section ( &CS time—t セー int i ー void f001 ( VO 土 d *p) return 0 ー for(i = i く 2 の i 十十 ) { EnterCritica lSection ( &cs 土 n し void f002(void *p) while(t 十 5 > time(NULL)); / * 他のスレッドが割り込む確率を高めるための printf("%d%n",ary[il I•eaveCriticalSection(&cs); cnt— 特集 1 セーフテイプログラミング 19
'Task:test2" が書き込まれる順番はバラバラ を開けない SH DENYWR を指定しているの たがって , こはエラーとして処理するの になっています。また , この結果は実行す で , f002 関数のなかの _fsopen 関数は Permis ではなく , ファイルが使用できるまで待つ るごとに変化します。 sion denied ( 許可の拒否 ) というエラーを起 ようにするべきです。その部分を変更した スレッド f001 と f002 の書き込みをそれぞ プログラムが List 17 で , 出力されたテキス こしているわけです。 トファイル test が Fig. 10 です ( 当然ですが れまとめて行いたい場合には , 通常の f 叩 en ここで , f002 関数ではエラーの場合は直 関数を使うのではなく , 排他制御を行える ちに戻っていますが , 排他制御によるエラ Permission denied 以外のエラーの場合い -fs 叩 en 関数を使ってファイルを開きます。 ーは通常のファイルのエラーとは違い , foo くら待ってもエラーは解消されないので , 排他制御とは , ファイルを開いている間 , 1 関数の処理が終われば解消されます。し どのエラーが起きたかをチェックしなけれ ほかのスレッドやプロセスはそのファイル 工ラー処理を修正 ( 解放されるまで待つ ) を開けないようにコントロールすることで す。ー fs 叩 en 関数を使って開いたファイルに 対して , ほかのスレッドやプロセスから , 書き込み・読み込みモードによって , 開く ことを許可したり , 禁止したりすることが できます (TabIe 4 ) 。 List 15 のプログラムを一 fs 叩 en 関数を使っ て書き直すと , List 16 のようになり , その 実行結果は Fig. 9 のようになります。画面 には「 fopen error[name:test2] : Permission denied 」というエラーメッセージが出てい ます。これは , f 。 02 関数がファイルを開こ うとしたときに , まだ , f001 関数がファイ ルを開いたままの状態だったので , 排他制 御が働いているのです。 fo 。 1 関数が書き込 みモードではほかのスレッドからファイル Fig. 8 List 15 によって作成されたテキストフ アイル test の中身 Task:test1 Task:test2 Task:test1 Task:test2 Task:test1 Task:test2 Task:test1 Task:test2 Task:test1 Task:test2 Task:test2 Task:test1 Task:test1 Task:test2 Task:test1 Task:test2 Task:test1 Task:test2 Task:test1 Task:test2 1St 1 7 # incl ude く stdio. h> ー #include く stdlib. h> #include く string. h> #include く e てて no. h > #include く process. h> #inc lude く time. h> # incl ude く share. h> ー volatile int cnt void fool(void *p) FILE *fp; int 土ー time—t し一 printf( ”宅 s start%n" を p while い ()p = -fsopen( "test" ,"a",SH-DENYWR) ) ) { if(errno ! = EACCES) { printf( "fopen error[name:%s] : %s%n",p,strerror(errno) cnt - return ー ー void f002(void *p) FILE *fp; 土 n し土ー time—t t; printf( ”宅 s start%n" ,p); while い ()p = -fsopen( "test" , ″ a ″ ,SH—DENYWR) ) ) { if(errno ! = EACCES) { printf( "fopen e てて 0 て [ na 爪 e : 宅 s ] : %s%n",p,strerror(errno) cnt- return ・ fo て ( i = の i く 1 の i 十十 ) { fprintf(fp, "Task:%s*n",p); fflush(fp); し = time(NULL); / * 時間の掛かる処理の代わり * / whi le(t+2>time(NULL) fclose(fp); cnt— for ( i = の i く 10 十十 ) { fprintf(fp, "Task:%s%nn,p); fflush(fp); t = time(NULL); / * 時間の掛かる処理の代わり * / while(t 十 2>time(NULL) fclose(fp); cnt- int main ( ) int て e セー cnt 十十一 て e セ = —beginthread(f001,1024, ntestl"); printf( "ret=%d%n" ,ret); if(ret -1 ) exit(l); cnt 十十一 ret = -beginthread(f002 , 1024 , "test2" printf( ”て et d n ″ ,ret); if(ret = = -1 ) exit(l); while(cnt); return 0 ー 16 C MAGAZINE 2000 6
たかどうか」をチェックします (List 11-2 ) 。 当然 , データの入力時もエラーチェック は欠かせません。 fgets 関数はファイルの終 端を検出するために戻り値をチェックして いるので , これでエラーチェックを兼ねる ことができます。ただ , 「正常終了したの か , 工ラーで終了したのかを区別したい場 合には , fe 仼 or 関数を使ってチェックする」 必要があります ( List11-3 ) 。 同様にデータの出力時もエラーチェック をします。「ゆ uts 関数が正しく書き込めた のかどうかをチェックするために , 戻り値 を調べる」必要があります田 stll -4 ) 。 最後に fclose 関数もファイルが正しく閉 じられなかった場合にはエラーを返すので , do { ex 辻 ( 1 printf ( ” fopen error*nn int cnt, err; char buf [ 512 FILE *fp; int main( ) #include く s セ d は b. h 〉 #include <stdio. h> 8 List e 「 rno の初期化 「エラーが起きた場合に警告を表示する」よ ① 0 ①セヲフ恤グラミング List 11 を見比べてください。 List 11 はラン タイムの安全性が高まっているとはいえ , List10 と比べるとあまりにも煩雑で , プロ グラムロジックの見通しが悪くなっていま List 1 ( 乙ルコピープログラム #include く s セ d 土 0. h > #include く stdlib. h> int main ( int argc , char *argv [ ] ) FILE *fpi, *fpo; char buf [ 512 fpi = fopen(argv[ll , を” fP0 = fopen(argv[2], ″ w ” while(NULL ! = fgets(buf,sizeof(buf),fpi)){ fputs(buf,fpo); fclose(fpi); fclose(fpo); return 0 ー み工ラーが発生すると fclose 関数のエラー の書き込みが行われるので , そこで書き込 なわちディスクなどの物理的なメディアへ 関数が呼ばれたタイミングでファイル , す ていない場合があります。この場合 , fclose まで , 実際にはファイルに何も書き込まれ 処理する場合などは fcl 。 se 関数が呼ばれる ファリングされるので , 小さなファイルを ァイルの書き込みはゆ uts 関数のなかでノヾッ いるようですが , 多くの処理系においてフ けだから・・・・・」と終了処理を軽視する方も 「あとはアプリケーションを終了するだ うにします ( List11-5 ) 。 えたことになります。もう一度 , List 10 と 以上でひととおりのエラーチェックを加 となります。 if(!(fp = fopen("test","r")))( int cnt , err ・ char buf[ 512 FILE *fp; int main( ) #include く string. h> #include <stdlib. h> #include <stdio. h> 9 List pe 「「 0 「関数によるエラーメッセージ表示を行う errno = の / * f て ead 関数の前に e てて no を 0 で初期化 * / cnt = fread(buf,l,sizeof(buf) ,fp); err = errno; / * f 肛 ite 関数が e てて no を上書きする可能性があるので return 0 ー fclose(fp); printf( ” Er て no : 宅 d 基 n ″ ,err); = cnt } while( sizeof(buf) fwrite(buf,l,cnt ,stdout); e て r に退避 * / do { perror( "fopen でエラーが発生” / * fprintf( stderr, ” fopen でエラーが発生 : ,strerror(errno) ) と同じ * / exit( 1 errno = / * fread 関数の前に e て rno を 0 で初期化 * / cnt = fread(buf,l,sizeof (buf) ,fp); err = e てて n 丐 / * fw て e 関数が errno を上書きする可能性があるので err に退避 * / fwrite(buf,l,cnt,stdout); } while(sizeof(buf) ー cnt printf( ” Er て no : 宅 d 基 n ” ,err); fclose(fp); return 0 ー Fig. 6 使い方を間違えるとアプリケーションエラーとなるプログラム Fig. 5 pe 「「 0 「関数を用いたエラーメッセージ表示 ー」コマンドフロンフト Microsoft Ⅲ「 d 2000 CVersicVI 5. 圓 .2195 ] ℃ ) Cuwv 「国量 1985 -1999 M に「 0S0 日新「 p. C:V>List9 t 等エラーが発生 : h file 0 「 di 「い「 y 0 : V > い ccn>test に貶 2000 6 ) : わ凵 st 9 幽ー貶 2000 6 E 「ロ浦 : 0 」ゴ凶 ー・うマンドプ 0 ンプト Micro•:»ft flil ァケーションエラー (C) Ccoy 「 igh I):V 〉 Dir 工ラー生ため、 0 。 x 。を終 7 いす。プログラムをもう一度す 」る必驫蟲 コゴ凶 ドライブ C ー ボリューム C: のディレ , 」 000 / 04 / 14 19 : 39 00 / 04 / 14 16 : 22 , 000 / 04 / 16 18 : 14 」 000 / 04 / 16 03 : 45 3000 / 04 / 16 18 : 26 工ラーログを作成して ( はす。 く OIR> く田 R> く田 R 〉 2 のファイル 3 ー墅 ) デイしクトリ 0 第代 s and ings 2 新凵 STI 0. C 51 , 200 凵 STI 0. EÆ P 「 og 「 am Files 51 , 486 バイト 1 .615 , 994 , 880 バイトの空き領域 な V 〉 L i st 1 0 L i 引 1 0 ェ t est. t xt l):V>List10 0 : 0 」旧 test 特集 1 セーフテイプログラミング 1 1
Fig. 11 List 19 の実行結果 ( クリティカルセクションなし ) Fig. 12 List20 の実行結果 ( クリティカルセクションあり ) , コマンドフロンプト (C) Copyright 1985-1999 Microsoft Corp. :V>List19 - ロ X ロ X コマンドプロンプト (C) 「 ight 1985 ー 1999 M に「 0S0 日「 p. 〕 : わ Li 20 日ー - 材し 0 リ 1 ー《 ( リ「リイら 0 《 / 」 1 ー へのアクセスとで , 処理が異なるというこ ードを確認するためには WSAGetLastError ネットワークエラーへの対策 とはほとんどありません。ネットワーク固 関数を使います。 有の問題があるとすれば , ネットワーク用 WinSock では , WSA で始まる一連の関数 ネットワーク対策といっても , 前述した の API を使用した処理についてです。たと 群を使用します。 WinSock によるプログラ ローカルディスク上のファイルへのアクセ えば , List 23 は W1ndows ソケット (WmSoc ミングでは , ソケットオプジェクトを作成 k) を使用したプログラムですが , 工ラーコ スとファイルサーバ上のリモートファイル して , プログラムを実行する PC の IP アドレ クリティカルセクションの競合で止まるプログラム #include く windows . h> #include く stdio . h> #include く stdlib. h> #include く time . h> #include <process. h> void f001 ( vo 土 d * void f002(void * volatile 土 n セ cnt = 0 ー int aryl [ 20 ]; CRITICAL—SECTION csl; 土 n セ ary2 [ 20 ]; CRITICAL_SECTION cs2; int main ( ) ヨ int 土 + + ) { / * データの初期化 * / for(i = の土く 2 aryl [ 幻 for(i 土十十 ) { 土く 2 の ary2 [ 幻 = 19 ー InitializeCriticalSection(&cs1); InitializeCriticalSection(&cs2); cnt 十十一 if(¯0UL = = —beginthread(f001,1024,NULL) ) { pe てて 0 て ( "—beginthread e てて 0 て″ exit(l); List 21 void f001 ( VO 土 d *p) int 土ー セ土 me ーセセー EnterCriticalSection(&cs1); for(i = 土く 1 の土 + + ) { aryl [ 幻 = 19 ー セ = time(NULL); while(t + 5 > time(NULL)); / * 他のスレッドが割り込む確率を高めるための ダミー * / EnterCriticalSection(&cs2); for(; 土く 2 の土十十 ) { aryl[i] = a 2 は LeaveCriticalSection(&cs2 LeaveCriticalSection(&cs1); cnt— return 0 ー void f002 ( vo 土 d *p) time—t t; 卩 EnterCriticalSection(&cs2); fo て ( 土 = の土く 1 の土十十 ) { ary2 は ] t = time(NULL); while(t 十 5 > t 土 me ( ) / * 他のスレッドが割り込む確率を高めるための EnterCriticalSection(&cs1); for(; 土く 2 の土 + + ) { ary2 [ 幻 = aryl は LeaveCriticalSection(&cs1); LeaveCriticalSection(&cs2); cnt— cnt 十十一 ifCOUL = —beginthread ( f002 , 1024 , NULL ) ) { perror ( n—beginthread e てて 0 て″ ex 辻 ( 1 while(cnt); fo て ( 土 土く 2 の土十十 ) { printf ( ″ a て yl [%d ] i, a て YI [ 幻 for(i = の i く 2 の土十十 ) { printf ( ” ary2 [%d ] 1 , 矼 Y2 は ] DeleteCriticalSection(&cs1); DeleteCriticalSection(&cs2 20 C MAGAZINE 2000 6
を実現します。 列に値を設定する途中でⅱ me 関数を使って のスレッドがアクセスできないようにして これを使用しないからといって , C/C + + 時間を遅延させています。実はこの遅延処 おきます。 EnterCriticalSection 関数は , ほ のランタイム関数や W ⅲ dowsAPI 関数が工 理がないと , List 19 のような単純な処理で かのスレッドが引数のクリティカルセクシ ラーを起こすわけではありませんが , 意図 は , スレッド f001 の途中でスレッド f002 に ョンオプジェクトを使っていなければ , した結果を得られないという意味から考え 切り替わる確率が低すぎて問題点を見せら のオプジェクトを使用中の状態にして戻っ ると , 決して安全なプログラムとはいえま れないのです。事実 , List 19 ではこの遅延 てきます。このオプジェクトがほかのスレ 処理を無効にするだけでプログラムが一見 ッドによって使用中であれば , LeaveCritica せん。 List 19 は , クリティカルセクションを使 正常に動作するようになります。ただし , ISection 関数によってオプジェクトが解放 わずに一方のスレッド f001 関数で配列 a Ⅳ 本質的には問題が存在するので , 何千回に されるまで待ちます。このときは , オプジ に値を設定し , その値をもう一方のスレッ 1 回か何万回に 1 回かはわかりませんが , foo ェクトが解放されると , EnterCriticalSectio ド fo 。 2 関数で読み出すという例です。実行 2 関数の表示に異常が起きるということが n 関数から戻ってきます。 あります。実際のプログラミングでも単に 結果は , Fig. 11 のように f 。 01 関数が書き List19 の配列 a Ⅳへのアクセスにクリティ 換える前でもなく , 後でもない , 書き換え メッセージの表示などの主処理と直接関係 カルセクションを使うと List 20 のようにな の途中の結果を取り出しています。たとえ のない処理を追加しただけで , 動作が不安 ります (Fig. 12 ) 。 EnterCriticalSection 関数 ば , これが 20 個の装置の 1 組のサンプリン 定になることがあります。 List 19 のような や LeaveCriticaISection 関数の引数となるク グデータのようなものだとすると , 1 回目 問題はこのようなバグの原因となることが リティカルセクションオプジェクトはあら のサンプリングデータと 2 回目のサンプリ 予想されます。 かじめ , InitializeCriticaISection 関数を使っ ングデータが混じった情報は , 何の役にも 一般にはこういった複数のスレッドにま て作っておきます。使用済みとなったら , D 立ちません。 たがる共有リソースへのアクセスには , ア eleteCriticalSection 関数でオプジェクトを削 ところで , List19 を見ると f 。 ol 関数で配 クセス前に EnterCriticalSection 関数でほか 除します。 List 18 GetLastError 関数と Fo 「 matMessage 関数の使用例 , 1 一 1 #include く stdio . h> #include く stdlib. h> ヨ #include く string. h> # inc lude く e てて no . h > #inc lude く process. h> #include く time. h> #include く share. h> #inc lude く windows. h> vo latil e 土 n セ cnt = の void f001 ( void *p ) FILE *fp; int 土ー time—t t; DWORD e てて 0 て COde ー LPSTR lpMsgBuf; printf( ”宅 s start%nn ,p); while( ! ()p = -fsopen( "test" , ″ a ″ ,SH-DENYWR) ) ) { = GetLastError ( if(errorcode ! = ERROR—ACCESS—DENIED) { FormatMessage ( FORMAT—MESSAGE—ALLOCATE—BUFFER ー FORMAT—MESSAGE—FROM—SYSTEM ー FORMAT—MESSAGE—IGNORE—INSERTS, NULL, errorcode, MAKELANGID ( LANG—NEUTRAL , SUBLANG—DEFAULT ) , (LPTSTR) &lpMsgBuf , 0 ,NULL ); printf( "fopen error[name:%s] : 宅 s 基 n ” ,p, lpMsgBuf LocaIFree( IpMsgBuf); cnt— return ー FILE *fp; int time—t セー DWORD errorcode; LPTSTR lpMsgBuf; printf( ”宅 s start*nn,p); while( ! ()p = -fsopen( "test" , "a",SH-DENYWR) ) ) { errorcode = GetLastError ( if(errorcode ! = ERROR—ACCESS—DENIED) { FormatMessage ( FORMAT—MESSAGE—ALLOCATE—BUFFER ー FORMAT—MESSAGE—FROM—SYSTEM ー FORMAT—MESSAGE—IGNORE—INSERTS, NULL , errorcode , MAKELANGID(LANG—NEUTRAL, SUBLANG—DEFAULT) , (LPTSTR) &lpMsgBuf, O,NULL printf( "fopen error[name:%s] : s 基 n ” ,p, IpMsgBuf); Local Free ( IpMsgBuf cnt— return ー int main ( ) int て e セー cnt 十十一 ret = —beginthread(f001,1024, ” tes し 1 ” printf( ”て e に = 宅 d 基 n ” ,ret); if ( て e し ー 1 ) ex 辻 ( 1 cn セ十十一 ret = —beginthread(f002,1024, "test2" printf( ″て et = d 基 n ” , て e セ 土 f ( て e し -1 ) exit(l); while(cnt); return 0 ー for(i=0 く 1 の土十十 ) { fprintf(fp, "Task:%s%n",p); ff lush(fp); t = time(NULL); / * 時間の掛かる処理の代わり * / whi le(t 十 2>time(NULL) fclose(fp); cnt- fo て ( i=0 く 10 十十 ) { fprintf (fp,"Task:%s*n",p); fflush(fp); セ = time(NULL); / * 時間の掛かる処理の代わり while(t 十 2>time(NULL) fclose(fp); cnt— void f002 ( void *p ) 18 C MAGAZINE 2000 6
スにそのオプジェクトをバインドし , 通信 先の PC の IP アドレスとコネクトすることで , 通信状態を確立するようになっています 0 #include く stdio. h> #include <time . h> Fig. 13 スレッド A リソースの競合 リソース 2 EnterC 「 iticalSection リソース 1 EnterCriticalSection リソース 1 空き スレッド リソース 2 空き スレッド スレッド B Ente 「 C 「 iticalSection リソース 2 EnterCriticalSection リソース 1 セヲフラミ クで起きるエラーとそれに対応する WSAGe 数で取り出すことができます。ネットワー 詳細なエラーコードを WSAGetLastError 関 WSA で始まる関数でエラーが起きると , WinSock を使う場合のエラー処理 ルを実装しないという使い方もあります。 りも速度を優先して , 工ラー訂正プロトコ ムしなければなりませんが , 工ラー訂正よ トコルを定義して , それに従ってプログラ 工ラーなどが起きたときの再送要求のプロ UDP プロトコルを使用する場合には通信 ベルでその対策をする必要はありません。 うで行われるため , ソケットを利用するレ ーへの対応処理はソケットライプラリのほ 用する場合には , これらのランタイムエラ 通信というとデータ工ラーへの対策や , 再送要求などが必要だと思われますが , ソ ケットオプジェクトを TCP プロトコルで使 t レ stE な関数が返す工ラーメッセージを Tabl e5 に示します。ちょうど , 通常の Windows の OS レベルエラーコードを GetLastError 関 クリティカルセクションの競合がないプログラム ヨ #inc lude #incl ude <process. h> #include <stdlib. h> <Windows . h> ary2 [ 幻 a 1 は ] for(i = 土く 2 の土十十 ) { for(i = 土く 2 の土十十 ) { int int main ( ) CRITICAL—SECTION cs2 int ary2 [ 20 CRITICAL—SECTION CSI 土 n セ aryl [ 20 volatile int cnt = void f002(void * void f001 ( void * = 19 ー 1 1 ァータの初期化 * / InitializeCriticalSection(&cs1); InitializeCriticalSection(&cs2); cnt 十十一 ifCOUL = = -beginthread(fool ,1024,NULL) ) { pe てて 0 て ( ”—beginthread e てて 0 て” ex ( 1 cnt 十十一 ifCOUL = = —beginthread(f002,1024,NULL) ) { perror ( "—beginthread e て ro て” List 22 void fool(void *p) 1 ー time—t t; while(t 十 5 > me ( 阯 ) / * 他のスレッドが割り込む確率を高めるための t = time(NULL); tmpary[i] = 矼 YI は fo て土く 2 の土十十 ) { EnterCriticalSection(&cs1); tm は ] for(i = i く 1 土十十 ) ( int セ mpa て y [ 20 time—t int 新 void f002(void *p) cnt— LeaveCriticalSection(&cs1); a て yl は ] = tmpary[i]; for(i = の i く 2 土 + + ) { EnterCriticalSection(&cs1); LeaveCriticalSection(&cs2); while(t 十 5 > time(NULL)); / * 他のスレッドが割り込む確率を高めるための t = time(NULL); tmpary[i] = ary2[i]; for(; i く 2 土十十 ) ( EnterCritica lSection ( &cs2 = 19 tmpary[i] fo て ( 土 = i く 1 土十十 ) { 土 n に tmpary [ 20 ex 辻 ( 1 while(cnt); fo て ( 土 = の i く 2 i + + ) { printf("aryl[*d] = 宅 d n ” for(i = i く 2 i 十十 ) { printf( ″ a て y2 は d ] = d n ” DeleteCriticalSection(&cs1); DeleteCriticalSection(&cs2); return 0 ー i, yl は ] 土 , a 2 [ 幻 LeaveCriticalSection(&cs1); EnterCriticalSection(&cs2); for(i = 土く 2 土十十 ) { ary2[i] = tmpary[i); LeaveCritical Section ( &cs 2 特集 1 セーフテイプログラミング 21
も , EXE 実行可能ファイル内の matherr ル ーチンをユーザが定義した matherr 関数に 置き換えることができます。 ただし , MSVCRI'. DLL の DLL 内に含まれ ている matherr ルーチンをユーザ定義の m athe 関数で置き換えることはできません。 これは , EXE ファイルの場合にはリンク時 に EXE ファイルのなかでユーザが定義して いる一 mathe 仼関数にリンクする指示ができ ますが , DLL ファイルのなかの数学関数は , DLL ファイルをリンクしたときにはすでに DLL ファイルのなかのデフォルトの mather r 関数とリンクされているので , あとから E XE のなかで定義されている matherr 関数に 置き換える処理ができないためです。 EXE ファイルのなかで数学関数がエラー を起こした場合は , EXE ファイルのなかで 定義されている一 matherr 関数が呼び出され ますが , DLL ファイルのなかでエラーが起 きた場合は , DLL ファイルのなかで定義さ れているテフォルトの mathe 関数が呼び 出されることを覚えておいてください。 スレッドへの対策 かって , シングルタスク OS の MS-DOS が PC 用の主力 OS として使われていたころは , スレッドとプロセスの終了 #include く stdio. h> #include <process. h> volatile int cnt void foo(void * int main ( ) cnt 十十一 if(&OUL = = -beginthread(f00,1024,NULL) ) { pe て ro て ( ”スレッドが作れません” exit( 1 / * 次の文を有効にしないと、スレッドのメッセージが表示されない。 14 cnt— p て土 n セ f ( ”スレッドからのメッセージ基 n ” void foo(void *p) return 0 ー / * while(cnt); * / 複数のタスクによるリソースの競合を気に することなくプログラムを作ることができ ました。画面表示でも , I/O 入出力でも , ほかに動いているタスクはないので , それ らを独占してかまいませんでした。つまり , プログラマは「いつでもどこにでもアクセス できる」という前提でプログラムできたわ けです。 しかし , 今日の主力 OS である Windows は マルチタスク OS です。しかも , スレッドと いう軽量版のプロセスとでもいえるタスク Fig. 7 ー mathe 「 r 関数で「 etva の値を強制的に 0.0 に変更 三コマンドプロンプト 第 0 「 050 十 t "indows 2000 [ ion 5. 囲 .2195 ] ( 0 ) 000Y 「 igh 士 1935 -1999 M に「 0S0 Co 「 p. 0 : ¥〉 L i 鹹 1 3 工ラーを起こした関数 : 石 i n asin: ÜO Ⅲ e 「 - 「 0 「 土ゴ asin: Math a 「 gument 工 000000 14 C MAGAZINE 2000 6 」団凶 を複数動かすことが可能になっています。 もはや , 「いま動いているプログラムがすべ てのリソースを独占できる」という前提で プログラミングすることはできません。ほ かのプログラムがリソースを使っている可 能性 , いま動いているプログラムが使って いるリソースが解放されるのを待っている タスクがある可能性を考慮に入れたプログ プロセスの終了 ラムを作らなくてはなりません。 もエラーにはならず , 単に処理が最後まで していない状態で元のプロセスを終了して る必要があります。スレッドの処理が終了 スレッドが処理を終えていることを確認す で , プロセスを終了するときにはすべての 各スレッドは並行して実行されているの ので , OS が自動的に全スレッドを終了します。 なってリソースをムダ遣いすることになる ロセスに属さない幽霊のようなスレッドと ないまま残っているスレッドがあると , プ す。もし , 元のプロセスが終了しても終了し かのスレッドも自動的に終了してしまいま ると , 同じプロセスから実行されているほ レッドのひとつがプロセス終了の処理をす セス ) で , そのプロセスから実行されたス ルチスレッド ) を実行するプログラム ( プロ て注意を要する点です。複数のスレッド ( マ これは , スレッドプログラミングにおい
数で取り出せるのと同じです。 GetLastErro ションのなかで検出し , RaiseException 関 例外ハンドラ r 関数と同様 , FormatMessage 関数を使っ 数を呼び出して OS に知らせるものがありま てエラーコードから対応するエラーメッセ す。 Visual C + + で実装されている C 言語の例外 ージを取得することができます。 C + + 言語は 言語仕様として例外を扱う ハンドラの構文は , 次のとおりです。 先ほどサンプルとして示した List23 を見 ことが可能で , try throw, catch キーワー るとわかるように WinSock レベルのプロ ドを使って記述します。しかし , C に プログラムプロック 1 「コロロ グラムでは , 通常の非ネットワークプログ はこのような例外処理を扱う機能はありま ラムと何ら変わりなく , 関数の戻り値をチ せん。ただし , W1ndows 上では OS として例 _except( フィルタ ){ ェックするだけでネットワークエラーに対 外をサポートしているので , Visual C + + な プログラムプロック 2 処できます。 どは言語仕様を拡張して , C 言語でも例外 を扱えるようになっています。 プログラムプロック 1 の文は , 無条件に Windows 工ラーハンドラ このような例外処理機能 ( 構造化例外処 実行されます。プログラムプロック 1 の実 理 ) を採用することにより , コードの信頼 行中は , フィルタとプログラムプロック 2 「例外」とは , 「不測の事象 , あるいはプ 性を高めることができます。たとえば , 「予 に定義されている例外ハンドラが有効にな ロセスの正常な進行を妨げる現象」をいい , 期しない終了」のような例外が発生した場 ります。 合に , メモリやファイルなどのリソースを ハードウェアとソフトウェアの両方から検 プログラムプロック 1 から呼び出された 出できます。 確実に解放したり , 閉じたりすることが可 関数のなかで例外が発生するとフィルタと 代表的なハードウェア例外としては , ゼ 能になるし , メモリ不足などの特別な問題 プログラムプロック 2 が例外ハンドラとし 口による除算や数値型のオーパフローなど に関しても goto 文に依存したり , 戻りコー て機能します。また , プログラムプロック があり , ソフトウェア例外は , OS により検 ドを徹底してテストしなくても簡潔な構造 1 の実行中に例外が発生すると , より優先 出される特殊ケースのほかに , アプリケー 化コードで対処することができます。 度の高いハンドラに制御が渡らないかぎり , List 23 WinSock を使用したプログラム #include <windows . h> #include く s セ d 土 0. h > #include く stdlib. h> void checkLastError ( void WORD wVersionRequested; WSADATA wsaData ー int err ・ SOCKET s ー int main( ) struct sockaddr—in name , targetname ー char buf [ 256 char hostname[256 struct hostent * hostentry ー int cnt ー wversionRequested = MAKEWORD( 2 , 0 e てて = WSAStartup( wVersionRequested, &wsaData if ( err ! = 0 ) { printf ( "WinSock is not supported%n" return 0 ー targetname. sin-addr. s—addr = * ( unsigned long * ) hostentry->h-addr; if(connect(s,(struct sockaddr * )&targetname,sizeof(name) ) ) { checkLastError ( printf ( "ConnectedVn ″ struct sockaddr—in * ss; (struct sockaddr—in * ) & 町 SS printf( "addr family:%d, po てセ : 宅 d , 宅 d. 宅 d. 宅 d. 宅 d n ” ss—>Sin—family,ss—>Sin—port,ss—>Sin—addr. s—net, ss—>sin—addr. s—host,ss->sin—addr. s—lh, ss->sin—addr. s—impno); whil e ( 1 ) { fgets(buf,sizeof(buf) ,stdin); send(s,buf,strlen(buf),0); printf ( "buf=%s%nn ,buf); if(strcmp(buf , "quit%n")==O)break; cnt = recv(s,buf,sizeof(buf) , 0 buf[cnt] printf ( "RECV :%s%n", buf cl osesocket ( s WSACIeanup( return 0 ー void checkLastError ( ) LPVOID lpMsgBuf; int ー e = WSAGetLastError ( printf( ” Er て 0 て # : 宅 d 基 n ” , le); FormatMessage ( FORMAT—MESSAGE—ALLOCATE—BUFFER ー FORMAT—MESSAGE—FROM—SYSTEM ー FORMAT—MESSAGE—IGNORE—INSERTS, NULL , , MAKELANGID(LANG—NEUTRAL, SUBLANG-DEFAULT) , / * デフォルト言語 * / ( LPTSTR ) & lpMsgBuf , NULL printf( s ″ , IpMsgBuf LocaI Free ( IpMsgBuf WSACIeanup( exit(l); printf( "Version:%d. 宅d(宅S)基n” ,LOBYTE( wsaData. wversion HIBYTE ( wsaData. wversion ) , wsaData. szDescription socket(AF—INET,SOCK_STREAM, IPPROTO—TCP); S if ( s = INVAL I D—SOCKET ) { checkLastError ( printf ( "Get socket%nn name. sin—fami ly = AF—INET ・ name. sin—port = 0 ー name. sin—addr. s—addr = htons( INADDR—ANY); if(bind(s, (struct sockaddr *)&name,sizeof (name) ) ) { checkLastError ( printf( "Binded%nn targetname. sin—fami ly = AF—INET; targetname. sin—port = htons ( 7 hostentry = gethostbyname( "ftp.softboat . jp" ・ CO 22 C MAGAZINE 2000 6