C++ ライブラリ・リファレンス

第 4 章 マルチスレッド環境における iostream ライブラリの使用

本章では、マルチスレッド環境における入出力のために、libC ライブラリと libiostream ライブラリの iostream クラスを使用する方法を説明します。また、iostream クラスから派生クラスを作成して、ライブラリ機能を拡張する例も示します。ただし、ここで説明する内容は、マルチスレッド環境で実行する C++ コードを書くための解説ではありません。

この章の説明は、古い iostream (libC およびlibiostream) に対してのみ該当し、C++ 標準ライブラリに組み込まれている新しい iostream (libCstd) には該当しません。

マルチスレッド

マルチスレッド (MT) は、マルチプロセッサ上で実行するアプリケーションを高速化する強力な機能です。マルチスレッド機能を使用することにより、マルチプロセッサとシングルプロセッサの両方のアプリケーションの構造を簡素化することができます。iostream ライブラリが修正されたため、マルチスレッド環境で実行するアプリケーション、すなわち Solaris 2.2、2.3、2.4、2.5、2.5.1、2.6、7 で実行するときにマルチスレッド機能を利用するプログラムで、libC ライブラリのインタフェースを使用することができるようになりました。iostream インタフェースは変更されましたが、旧バージョンのシングルスレッド機能だけを使用するアプリケーションには影響ありません。

マルチスレッド環境で正しく実行するためには、ライブラリが 「MT-安全」として定義されていなければなりません。そのためには、一般にすべての公開関数が再入可能になっていなければなりません。libC ライブラリでは、複数のスレッドに共有されるオブジェクト (C++ クラスのインスタンス) の状態を変更しようとするマルチスレッドに対して、保護機能が提供されています。ただし、iostream オブジェクトの「MT-安全」のスコープは、オブジェクトの公開メンバー関数の実行中に限られます。


注意 - 注意 -

アプリケーションが、libC ライブラリの「MT-安全」オブジェクトを使用しているというだけで、自動的にマルチスレッド環境での安全性が保証されるわけではありません。アプリケーションが「MT-安全」となるのは、マルチスレッド環境での実行が想定されているアプリケーションの場合だけです。


「MT-安全」の iostream ライブラリの構成

「MT-安全」の iostream ライブラリの構成は、「MT-安全ではない」(マルチスレッド環境では使用できない) iostream ライブラリの構成と少し違います。「MT-安全」の iostream ライブラリから公開されているインタフェースは、iostream クラスとその基底クラスすべての公開および限定公開メンバー関数しか参照しないことは他のバージョンと同じですが、クラス階層が異なります。詳しくは、「iostream ライブラリのインタフェースの変更」を参照してください。

本来のコアクラスは、名前に接頭語 unsafe_ が付けられました。表 4-1 に、iostream パッケージのコアクラスを示します。

表 4-1 コアクラス

クラス 

内容 

stream_MT

MT-安全の基底クラス 

streambuf

バッファーの基底クラス 

unsafe_ios

エラー状態やフォーマット状態などの、さまざまなストリームクラスに共通の状態変数を含むクラス 

unsafe_istream

streambuf から取り出した文字シーケンスのフォーマット付きまたはフォーマットなし変換をサポートするクラス

unsafe_ostream

streambuf に格納される文字シーケンスのフォーマット付きまたはフォーマットなし変換をサポートするクラス

unsafe_iostream

入出力両方向の操作のために unsafe_istream クラスと unsafe_ostream クラスを結合するクラス

「MT-安全」の各クラスは、基底クラス stream_MT から派生します。streambuf を除く「MT-安全」クラスもまた、すべて名前が unsafe_ で始まる既存の基底クラスから派生します。次に例を示します。


class streambuf: public stream_MT { ... };
class ios: virtual public unsafe_ios, public stream_MT { ... };
class istream: virtual public ios, public unsafe_istream { ... };

stream_MT クラスは、それぞれの iostream クラスを「MT-安全」にするために必要な相互排他ロック機能を提供します。また動的にロックとロック解除を行う機能も提供し、「MT-安全」特性の動的変更を可能にします。入出力変換とバッファー管理の基本機能は、名前が unsafe_ で始まるクラスにまとめられており、「MT-安全」にするためにライブラリに追加した機能は、その派生クラスに限られます。「MT-安全」の各クラスに入っている公開および限定公開メンバー関数は、名前が unsafe_ で始まる基底クラスと同じです。「MT-安全」のクラスの各メンバー関数はラッパーとして働き、オブジェクトのロック後、名前が unsafe_ で始まる基底クラスの同じ関数を呼び出し、最後にそのオブジェクトのロックを解除します。


注 -

streambuf クラスは、名前が unsafe_ で始まるクラスから派生したクラスではありません。streambuf クラスの公開および限定公開メンバー関数は、ロック機能により再入可能になります。_unlocked という接尾辞の付いたロックなしのものも提供されています。


公開変換ルーチン

iostream インタフェースには、マルチスレッド環境で使用しても安全で、かつ再入可能な公開関数がいくつか追加されています。各関数の追加引数として、ユーザー定義バッファーを指定します。次に関数の内容を示します。

表 4-2 再入可能な公開関数

関数 

内容 

char *oct_r (char *buf, int buflen, long num, int width)

数値を 8 進表示した ASCII 文字列へのポインタを返します。幅がゼロ以外の場合は、フォーマット用のフィールド幅とみなされます。戻り値がユーザー提供バッファーの先頭を指していることは保証されません。 

char *hex_r (char *buf, int buflen, long num, int width)

数値を 16 進表示した ASCII 文字列へのポインタを返します。幅がゼロ以外の場合は、フォーマット用のフィールド幅とみなされます。戻り値がユーザー提供バッファーの先頭を指していることは保証されません。 

char *dec_r (char *buf, int buflen, long num, int width)

数値を 10 進表示した ASCII 文字列へのポインタを返します。幅がゼロ以外の場合は、フォーマット用のフィールド幅とみなされます。戻り値がユーザー提供バッファーの先頭を指していることは保証されません。 

char *chr_r (char *buf, int buflen, int chr, int width)

文字列 chr が含まれた ASCII 文字列へのポインタを返します。幅がゼロ以外の場合は、文字列には width 個のスペースに続いて chr が含まれます。戻り値がユーザー提供バッファーの先頭を指していることは保証されません。

char *form_r (char *buf, int buflen, const char *form,...)

フォーマット文字列 format に続く引数を sprintf でフォーマットした文字列へのポインタを返します。バッファーには、フォーマット済み文字列を入れるための十分な長さが必要です。


注意 - 注意 -

旧バージョンの libC ライブラリとの互換性を保つために提供されている iostream ライブラリの公開変換ルーチン (oct、hex、dec、chr、form) は、「MT-安全」ではありません。


「MT-安全」の libC ライブラリのコンパイルとリンク

マルチスレッド環境で実行するために、libC ライブラリの iostream クラスを使用して作成したアプリケーションについては、ソースコードのコンパイルとリンク時に -mt オプションを指定する必要があります。このオプションを指定すると、前処理部には -D_REENTRANT が、リンカーには -lthread が渡されます。


注 -

libC および libthread とのリンクには、-lthread ではなく -mt を使用してください。-mt オプションを使用すると、ライブラリの正しい順番でのリンクが保証されます。誤って -lthread オプションを使用すると、そのアプリケーションは正しく動かないことがあります。


iostream クラスを使用するシングルスレッド用のアプリケーションには特別なコンパイラやリンカーオプションは必要ありません。デフォルトで、コンパイラは libC ライブラリとリンクします。

「MT-安全」の iostream ライブラリの制限

iostream ライブラリのマルチスレッドに対する安全性に制限があるのは、iostream ライブラリで使用しているプログラミング手法の多くが、共有 iostream オブジェクトを使用するマルチスレッド環境では正しく実行できないためです。

エラー状態のチェック

「MT-安全」にするには、次の例のように、エラーを起こすような入出力操作を伴う危険領域では、入出力のエラーを調べる必要があります。


例 4-1 エラー状態のチェック

#include <iostream.h>
#include <rlocks.h>
enum iostate { IOok, IOeof, IOfail };

iostate read_number(istream& istr, int& num)
{
       stream_locker sl(istr, stream_locker::lock_now);

       istr >> num;

       if (istr.eof()) return IOeof;
       if (istr.fail()) return IOfail;
       return IOok;
}

この例では、stream_locker オブジェクトである sl のコンストラクタが istream オブジェクトの istr をロックしています。sl のデストラクタは、read_number の終了時に呼ばれ、istr のロックを解除します。

最後のフォーマットなし入力操作で抽出された文字の取得

「MT-安全」にするには、最も近いフォーマットなし入出力とそれに続く gcount 呼び出しの間に、iostream オブジェクトを排他的に使用するスレッド内で gcount 関数の呼び出しを行う必要があります。次の例を参照してください。


例 4-2 gcount の呼び出し

#include <iostream.h>	
#include <rlocks.h>
void fetch_line(istream& istr, char* line, int& linecount)
{
       stream_locker sl(istr, stream_locker::lock_defer);

       sl.lock(); // ストリーム istr をロック
       istr >> line;
       linecount = istr.gcount();
       sl.unlock(); // istr のロック解除
       ...
}

この例では、stream_locker クラスの lock メンバー関数および unlock メンバー関数がプログラム中で相互排他領域を定義しています。

ユーザー定義の入出力操作

「MT-安全」にするには、ユーザー定義型に対して定義された入出力操作で特定の操作順序を持つものは、ロックして危険領域を定義する必要があります。次の例を参照してください。


例 4-3 ユーザー定義の入出力操作

#include <rlocks.h>
#include <iostream.h>
class mystream: public istream {

       // その他の定義...
       int getRecord(char* name, int& id, float& gpa);
}

int mystream::getRecord(char* name, int& id, float& gpa)
{
       stream_locker sl(this, stream_locker::lock_now);

       *this >> name;
       *this >> id;
       *this >> gpa;
       return this->fail() == 0;
}

パフォーマンス

現バージョンの libC ライブラリの「MT-安全」のクラスを使用すると、シングルスレッドのアプリケーションの場合でもパフォーマンスのオーバーヘッドが起こります。 libC ライブラリの unsafe_ クラスを使用すると、オーバーヘッドはなくなります。

次の例のように、スコープ決定演算子を使用して unsafe_ 基底クラスのメンバー関数を実行することができます。


cout.unsafe_ostream::put('4');


cin.unsafe_istream::read(buf, len);


注 -

マルチスレッド対応のアプリケーションでは、unsafe_ クラスの使用は危険です。


次の例のように、unsafe_ クラスを使用する代わりに、cout オブジェクトと cin オブジェクトを「MT-安全ではない」として、通常の操作を使用することもできます。この場合、パフォーマンスはわずかに低下します。


例 4-4 「MT-安全」の禁止

#include <iostream.h>
cout.set_safe_flag(stream_MT::unsafe_object);   //「MT-安全ではない」に設定
cin.set_safe_flag(stream_MT::unsafe_object);    //「MT-安全ではない」に設定
cout.put('4');
cin.read(buf, len);

iostream オブジェクトが「MT-安全」である場合は、相互排他ロック機能が提供されてオブジェクトのメンバー変数が保護されます。このロック機能のため、シングルスレッド環境でのみ実行されるアプリケーションの場合は不要なオーバーヘッドがかかります。 次の例のように、iostream オブジェクトの「MT-安全」と「MT-安全ではない」を動的に切り換えて、パフォーマンスを向上させることができます。


例 4-5 「MT-安全ではない」の切り換え

fs.set_safe_flag(stream_MT::unsafe_object);//「MT-安全ではない」に設定
... さまざまな入出力操作を実行

iostream が複数スレッドに共有されない場合、たとえばスレッドが 1 つしかないプログラムや、各 iostream がそれぞれのスレッドに対して非公開なプログラムの場合は、 「MT-安全ではない」のストリームを使用しても問題ありません。

また次の例のように、プログラム内で明示的に同期を取らせる場合は、iostream が複数スレッドに共有される環境で「MT-安全ではない」の iostream を使用しても問題ありません。


例 4-6 「MT-安全ではない」のオブジェクトを使用して同期を取る方法

generic_lock() ;  
fs.set_safe_flag(stream_MT::unsafe_object) ;
... さまざまな入出力操作を実行
generic_unlock() ;

ここで、generic_lock と generic_unlock の関数は、相互排他、セマフォ、読み取り/書き込みのような単純型を使用した同期機構を提供するものであれば何でもかまいません。


注 -

このような目的で使用するには、libC ライブラリで提供されている stream_locker クラスをお勧めします。


詳細は、「オブジェクトのロック」を参照してください。

iostream ライブラリのインタフェースの変更

この項では、libC ライブラリを「MT-安全」にするために、libC ライブラリのインタフェースがどのように変更されたかを説明します。

新しいクラス

次の表は、iostream インタフェースに新しく追加されたクラスのリストです。


例 4-7 新しいクラス

stream_MT								
stream_locker								
unsafe_ios								
unsafe_istream								
unsafe_ostream								
unsafe_iostream								
unsafe_fstreambase								
unsafe_strstreambase								

新しいクラス階層

次の表は、iostream インタフェースに新しく追加されたクラス階層のリストです。


例 4-8 新しいクラス階層

class streambuf : public stream_MT { ... };
class unsafe_ios { ... };
class ios : virtual public unsafe_ios, public stream_MT  {...};
class unsafe_fstreambase : virtual public unsafe_ios { ... };
class fstreambase : virtual public ios, public unsafe_fstreambase { ... };
class unsafe_strstreambase : virtual public unsafe_ios { ... };
class strstreambase : virtual public ios, public unsafe_strstreambase { ... };
class unsafe_istream : virtual public unsafe_ios  { ... };
class unsafe_ostream : virtual public unsafe_ios  { ... };
class istream : virtual public ios, public unsafe_istream { ... };
class ostream : virtual public ios, public unsafe_ostream { ... };
class unsafe_iostream : public unsafe_istream, public unsafe_ostream { ... };

新しい関数

次の表は、libC インタフェースに新しく追加された関数のリストです。


例 4-9 新しい関数

class streambuf {
public:
     int sgetc_unlocked();
     void sgetn_unlocked(char *, int);
     int snextc_unlocked();
     int sbumpc_unlocked();
     void stossc_unlocked();
     int in_avail_unlocked();
     int sputbackc_unlocked(char);
     int sputc_unlocked(int);
     int sputn_unlocked(const char *, int);
     int out_waiting_unlocked();
protected:
     char* base_unlocked();
     char* ebuf_unlocked();
     int blen_unlocked();
     char* pbase_unlocked();
     char* eback_unlocked();
     char* gptr_unlocked();
     char* egptr_unlocked();					
     char* pptr_unlocked();						
     void setp_unlocked(char*, char*);				
     void setg_unlocked(char*, char*, char*);			
     void pbump_unlocked(int);					
     void gbump_unlocked(int);					
     void setb_unlocked(char*, char*, int);				
     int unbuffered_unlocked();					
     char *epptr_unlocked();						
     void unbuffered_unlocked(int);					
     int allocate_unlocked(int);					
};

class filebuf : public streambuf {
public:
     int is_open_unlocked();					
     filebuf* close_unlocked();					
     filebuf* open_unlocked(const char*, int, int=filebuf::openprot);

     filebuf* attach_unlocked(int);		
};

class strstreambuf : public streambuf {
public:
     int freeze_unlocked();		
     char* str_unlocked();				
};

unsafe_ostream& endl(unsafe_ostream&);					
unsafe_ostream& ends(unsafe_ostream&);					
unsafe_ostream& flush(unsafe_ostream&);					
unsafe_istream& ws(unsafe_istream&);
unsafe_ios& dec(unsafe_ios&);				
unsafe_ios& hex(unsafe_ios&);						
unsafe_ios& oct(unsafe_ios&);													
char* dec_r (char* buf, int buflen, long num, int width)
char* hex_r (char* buf, int buflen, long num, int width)
char* oct_r (char* buf, int buflen, long num, int width) 
char* chr_r (char* buf, int buflen, long chr, int width)
char* str_r (char* buf, int buflen, const char* format, int width = 0);
char* form_r (char* buf, int buflen, const char* format, ...) 

大域データと静的データ

マルチスレッド対応のアプリケーションでは、大域データと静的データをスレッド間で安全に共有することができません。各スレッドは独立して実行されますが、大域データと静的データへのアクセスはプロセス内のスレッド間で共有されています。そのような共有オブジェクトをあるスレッドが変更すると、プロセス内のその他の全スレッドがその変更の影響を受け、必要な間同じ状態を保つことが期待できなくなります。C++ では、クラスオブジェクト (クラスのインスタンス) の状態はメンバー変数の値で示されます。したがって、そのクラスオブジェクトが共有されていれば、他のスレッドからも変更されてしまいます。

マルチスレッド対応のアプリケーションで iostream ライブラリを使用し、iostream.h をインクルードしている場合、標準ストリーム (cout、cin、cerr、clog) は、デフォルトで大域的な共有オブジェクトとして定義されます。iostream ライブラリは「MT-安全」なので、iostream オブジェクトのメンバー関数の実行中は、共有オブジェクトの状態は他のスレッドからのアクセスや変更から保護されています。ただし iostream オブジェクトの「MT-安全」が有効なのは、そのオブジェクトの公開メンバー関数の実行中に限られます。次の例を考えてみましょう。


	int c;
	cin.get(c);

このコードでスレッド A が get バッファから次の文字を取り出し、スレッド A のバッファーポインタを更新します。ところが、スレッド A の次の命令がやはり get の呼び出しであっても、文字シーケンスからその次の文字が取り出されるという保証は libC ライブラリにおいてはありません。なぜなら、スレッド A による 2 つの get の呼び出しの間に、たとえばスレッド B が get の呼び出しを行う可能性があるからです。

共有オブジェクトとマルチスレッドにおけるこのような問題を解決する方法については、「オブジェクトのロック」を参照してください。

順次実行

iostream オブジェクトを使用すると、一連の入出力操作を「MT-安全」にしなければならない状況が頻繁に起こります。次のコードを例に考えてみましょう。


cout << " Error message:" << errstring[err_number] << "¥n";

このコードには、cout ストリームオブジェクトの 3 つのメンバー関数の実行が含まれています。 cout は共有オブジェクトですから、このコードをマルチスレッド環境で正しく実行するには、一連の入出力操作が危険領域として不可分命令的に実行されなければなりません。iostream クラスオブジェクトの一連の入出力操作を不可分命令的に実行するには、なんらかのロックを行う必要があります。

libC ライブラリでは、iostream オブジェクトの操作をロックするための stream_locker クラスが提供されています。stream_locker クラスについては、次の「オブジェクトのロック」を参照してください。

オブジェクトのロック

共有オブジェクトとマルチスレッドで起こる問題の最も簡単な解決方法は、iostream オブジェクトを 1 つのスレッドに対して局所的にして問題自体をなくしてしまうことです。そのためには、次のような方法があります。

ところが多くの場合 (たとえばデフォルトの共有標準ストリームオブジェクトの場 合)、オブジェクトを特定のスレッドの局所オブジェクトとすることが不可能で、他の解決策を探さなければなりません。

iostream クラスのオブジェクトに対する一連の操作を不可分命令的に実行するには、なんらかのロックを使用する必要があります。ロックを行うと、シングルスレッド用のアプリケーションの場合でもいくらかオーバーヘッドが起こります。ロックを使用するか、あるいは、iostream オブジェクトをスレッドの非公開オブジェクトとするかは、アプリケーションで採用しているスレッドモデルによります。

各スレッドが独立の場合と、複数スレッドが共同作業を行う場合

stream_locker クラス

iostream ライブラリでは、iostream オブジェクトに対する一連の操作をロックするための stream_locker クラスが提供されています。したがって、iostream のロックとロック解除を動的に設定することによるオーバーヘッドを最小にすることができます。

stream_locker クラスのオブジェクトを使用すると、ストリームオブジェクトに対する一連の操作を不可分命令的に実行することができます。たとえば、次の例では、ファイル内の位置を指定して、そこからデータを 1 ブロック読み込みます。


例 4-10 ロック操作を使用する例

#include <fstream.h>
#include <rlocks.h>
void lock_example (fstream& fs)
{
    const int len = 128;
    char buf[len];
    int offset = 48;
        stream_locker s_lock(fs, stream_locker::lock_now);
        . . . . .// ファイルをオープン
        fs.seekg(offset, ios::beg);
        fs.read(buf, len);
}

この例では、stream_locker オブジェクトのコンストラクタが相互排他制御域の開始を定義します。相互排他制御域では、一度に 1 つのスレッドしか実行されません。また、関数から戻った後で呼び出されるデストラクタでは、相互排他制御域の終了を定義します。したがって、stream_locker オブジェクトにより、ファイル内の特定の位置のシークと、ファイルからのデータの読み込みとが不可分命令的に実行されます。スレッド A がファイルからデータを読み込む前に、スレッド B がファイル内の位置を変えてしまうことができなくなるためです。

stream_locker オブジェクトのもう 1 つの使用方法として、相互排他制御域を明示的に定義する方法があります。次の例では、入出力操作とそれに続くエラー検査を不可分命令的に実行するため、stream_locker オブジェクトのメンバー関数 lock と unlock を呼び出します。


例 4-11 入出力操作とエラー検査を不可分命令的に実行

{
     ...
     stream_locker file_lck(openfile_stream,
                               stream_locker::lock_defer);
     ....
     file_lck.lock();  // openfile_stream をロック
     openfile_stream << "Value: " << int_value << "¥n";
     if(!openfile_stream) {
             file_error("Output of value failed¥n");
             return;
     }
     file_lck.unlock();  // openfile_stream のロックを解除
}

stream_locker についての詳細は、stream_locker(3) のマニュアルページを参照してください。

「MT-安全」のクラス

iostream クラスから新たなクラスを派生させて、機能を拡張したり特殊化したりすることができます。派生クラスのオブジェクトをマルチスレッド環境で使用するときは、派生クラスも「MT-安全」にする必要があります。

「MT-安全」のクラスを派生するときには、次の点を考慮してください。

オブジェクトの破壊

複数のスレッドで共有する iostream オブジェクトを削除するときは、すべての副スレッドがそのオブジェクトの使用を終了していることを、主スレッドで確認する必要があります。次の例で、共有オブジェクトを安全に破壊する方法を示します。


例 4-12 共有オブジェクトの破壊

#include <fstream.h>
#include <thread.h>
fstream* fp;

void *process_rtn(void*)
{
      // fp を使用する副スレッドの本体...
}

multi_process(const char* filename, int numthreads) 
{
      fp = new fstream(filename, ios::in);  // スレッド生成の前に
                                            // fstream オブジェクトを生成
      // スレッドの生成
      for (int i=0; i<numthreads; i++)
             thr_create(0, STACKSIZE, process_rtn, 0, 0, 0);

      // 全スレッドの終了待ち
      for (int i=0; i<numthreads; i++)
             thr_join(0, 0, 0);

      delete fp;                            // 全スレッドの終了後に
      fp = NULL;                            // fstream オブジェクトを破壊
}

アプリケーションの例

次の例では、複数のスレッドを使用するアプリケーションの例を示します。この例では、libC ライブラリの iostream オブジェクトを「MT-安全」で使用します。

このアプリケーションでは、スレッドを 255 個まで作成します。各スレッドはそれぞれ異なる入力ファイルから一度に 1 行ずつデータを読み込み、そのデータを出力ファイルに書き込みます。そのとき標準出力ストリーム cout を使用します。出力ファイルは全スレッドで共有するため、どのスレッドから出力されたかを示すタグを付けます。


例 4-13 iostream オブジェクトを「MT-安全」で使用

// <ストリングデータ> は、任意の印刷可能文字
// タグは、整数値を char 型として書き込むため、
// 出力ファイルを表示するときは、次のように od を使用する必要がある
//           od -c out.file |more

#include <stdlib.h>
#include <stdio.h>
#include <iostream.h>
#include <fstream.h>
#include <thread.h>

struct thread_args {
  char* filename;
  int thread_tag;
};

const int thread_bufsize = 256;

// 各スレッドのエントリルーチン
void* ThreadDuties(void* v) {
// 現スレッドの引数の取得
  thread_args* tt = (thread_args*)v;
  char ibuf[thread_bufsize];
  // スレッドの入力ファイルをオープン
  ifstream instr(tt->filename);
  stream_locker lockout(cout, stream_locker::lock_defer);
  while(1) {
  // 一度に 1 行を読み込み
    instr.getline(ibuf, thread_bufsize - 1, '¥n');
    if(instr.eof())
      break;

// 入出力操作を不可分命令的に実行するため、cout ストリームをロック
    lockout.lock(); 
  // データにタグを付けて cout に出力
    cout << (unsigned char)tt->thread_tag << ibuf << "¥n";
    lockout.unlock();
  }
  return 0;
}

main(int argc, char** argv) {
  // argv: 1+ 各スレッドのファイル名リスト
   if(argc < 2) {
     cout << "usage: " << argv[0] << " <files..>¥n";
     exit(1);
   }
  int num_threads = argc - 1;
  int total_tags = 0;

// スレッド ID の配列
  thread_t created_threads[thread_bufsize];
// スレッドのエントリルーチンへの引数の配列
  thread_args thr_args[thread_bufsize];
  int i;
  for( i = 0; i < num_threads; i++) {
    thr_args[i].filename = argv[1 + i];
// スレッドへのタグの割り当て − タグの値は 255 以下
    thr_args[i].thread_tag = total_tags++;

// スレッドの生成
    thr_create(0, 0, ThreadDuties, &thr_args[i], 
	       THR_SUSPENDED, &created_threads[i]);
  }

  for(i = 0; i < num_threads; i++) {
    thr_continue(created_threads[i]);
  }
  for(i = 0; i < num_threads; i++) {
    thr_join(created_threads[i], 0, 0);
  }
  return 0;
}