コンピュータのプログラミングにおいて、複数の処理を並行して実行したい場合、main()関数から順次実行されるメインスレッド以外に、並行動作させるサブスレッドを生成することで平行処理を実現します。
では、複数の処理を並行して実行させたいプログラムって、どんなプログラムでしょうか?
例えば「処理A」ボタンと「処理B」ボタンを置き、押された方の処理を実行するプログラムを作成する事とします。シングルスレッドでは、処理Aが終了するまで処理Bを行えません。
これがマルチスレッドであれば、「処理A」を行っている間にも「処理B」ボタンを受け付け、処理Bを行うことが出来るようになります。
今回のサンプルとしたチャットプログラムも同様で、相手に送信する通信文の入力を受け付け、送信している間は相手からの通信文は受信できません。
マルチスレッド化する事で通信文の入力および送信と、相手からの通信文の受信を並行して行えるようにできます。(注)
ここからC言語でマルチスレッド処理を行う手順についてサンプルプログラムにて説明します。
本サンプルプログラムはソケット通信で送受信を行うチャットプログラムの一部です。実際のソケット通信は関数化しており、本サンプルでは省略しています。
このプログラムはメイン関数(メインスレッド)でコンソールからの通信文の入力を受け付け、それをチャット相手に送信します。
並行して動作するサブスレッドではチャット相手からの通信文を受信し、コンソールへ表示します。
メインスレッドもサブスレッドも相手が終了してソケットが切断されるか、Ctrl-Dが入力されるまでループして通信を繰り返します。
- recv_thread()がメインスレッドと並行して動くスレッドになります。
引数の param はスレッドを生成する pthread_create()の第4引数が渡されます。スレッドに渡すパラメータがある場合は、これを使用します。
渡す引数が複数ある場合は、パラメータブロックを構造体や配列で作成し、それをポインタで渡すのが一般的です。 - end_flag、complete_flag はサブスレッドの動作を制御するためのフラグです。
メインスレッドとサブスレッドの間で相互の通知が必要ない場合は不要です。 - start_thread()はスレッドを起動する関数です。
スレッド起動にはpthread_create()を使用します。引数はスレッドID、スレッド属性、スレッド、スレッドへ渡す引数です。
スレッドIDはpthread_create()が生成したスレッドIDを取得するものなので、ポインタ渡しになることに注意してください。
スレッド属性はpthread_attr_init()で生成した属性を渡します。スレッド属性変更の必要が無ければNULLでかまいません。
スレッド属性にはデタッチ状態、スケジューリングポリシー、スケジューリングパラメータがあります。
デタッチ状態はとはスレッド終了時のリソース解放処理を変更するもので、PTHREAD_CREATE_DETACHEDに設定するとスレッド終了を待ち合わせるpthread_join()が不要になります。
詳しくはpthread_attr_initのmanページを見てください。 - terminate_thread()はスレッドを終了させる関数です。
end_flagをtrueにセットし、受信スレッドの繰り返し処理を終わらせます。その後、pthread_join()を呼んでスレッドの終了を待ち合わせます。
このサンプルコードではcomplete_flagを参照してスレッドの処理終了を待ち、スレッドが一定時間以内に終了しなかった場合はpthread_cancel()を呼び出し強制終了するようにしています。これはスレッドがループの中で待ちになるような場合への対応です。 - main()はメインスレッドで、コメントと合わせて読めばどんな処理をしているかは判るのでここでの説明は省略します。
============ ここから ============
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include <pthread.h>
#include "socket.h"
#include "console.h"
volatile bool end_flag;
volatile bool complete_flag;
void * recv_thread( void * param )
{
int fd = *((int *)param); // 引数で渡される通信ソケットのディスクリプタ
char message_buffer[79];
int rtn;
while( !end_flag )
{
// ソケットから通信文を受信する
rtn = receive_socket( fd, message_buffer, sizeof( message_buffer ));
// 通信文が取得できた場合
if ( rtn > 0 )
{
// 通信文をコンソールへ表示する
rtn = output_console( message_buffer );
}
// ソケットが切断された場合
else if ( rtn == 0 )
{
fprintf( stdout,"disconnected.\n" );
// ループから抜ける
break;
}
// ソケット通信エラーの場合
else
{
fprintf( stdout, "receive_soket error.\n");
// ループから抜ける
break;
}
}
end_flag = true;
complete_flag = true;
}
int start_thread( pthread_t * thread_id, int fd)
{
int rtn;
pthread_attr_t attr;
// スレッド属性オブジェクトを初期化し、値を設定する
pthread_attr_init( &attr );
pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );
end_flag = false;
complete_flag = false;
// 受信スレッドを生成する
rtn = pthread_create( thread_id, &attr, recv_thread, &fd );
// スレッド属性オブジェクトを破棄する
rtn = pthread_attr_destroy( &attr );
return rtn;
}
int terminate_thread( pthread_t thread_id )
{
int rtn;
// 受信スレッドを終了させる
end_flag = true;
// 受信スレッドの処理終了を1秒まで待つ
for ( int i = 0 ; i < 10 && !complete_flag ; i++ )
{
struct timespec tm = { 0, 100*1000*1000 };
nanosleep( &tm, NULL );
}
// スレッドが終了しなかった場合
if ( !complete_flag )
{
// 強制的にスレッドを終了させる
rtn = pthread_cancel( thread_id );
}
// 受信スレッドの終了を待つ
rtn = pthread_join( thread_id, NULL );
return rtn;
}
int main( int argc, char *argv[] )
{
int fd;
pthread_t thread_id;
char message_buffer[79];
int rtn;
// サーバーソケットを生成し、接続されるのを待つ
fd = create_socket_server();
// 接続失敗の場合、終了する
if ( fd == -1 )
{
exit(1);
}
// 受信スレッドを生成する
rtn = start_thread( &thread_id, fd );
// 通信文の送受信を繰り返す
while( !end_flag )
{
// コンソールから入力された通信文を取得する
rtn = input_console( message_buffer, sizeof( message_buffer ));
// 通信文が取得できた場合
if ( rtn > 0 )
{
// ソケットで通信文を送信する
rtn = send_socket( fd, message_buffer );
}
// Ctrl-Dが入力された場合
else if ( rtn == 0 )
{
fprintf( stdout, "disconnect.\n");
// ループから抜ける
break;
}
// 入力エラーの場合
else
{
fprintf( stdout, "input_console error.\n");
// ループから抜ける
break;
}
}
// 受信スレッドを終了させる
rtn = terminate_thread( thread_id );
// ソケットをクローズする
destroy_socket( fd );
exit(0);
}
============ ここまで ============
注) 非同期通信を利用してプログラミングすることでも、同様の動作を実現できます。
上記サンプルプログラムを使用して発生した障害,問題などに関して,プログラムの作者および(株)アイズ・ソフトウェアは一切の責任を負いません。
使用はご自身の責任で行ってください。