スレッド・プログラミング

分散システム

                                       電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/dsy href="http://www.hlla.is.tsukuba.ac.jp/~yas/coins/">http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.hlla.is.tsukuba.ac.jp/~yas/index-j.html

■スレッド・プログラミング

特別協賛:オペレーティング・システムII。

◆スレッドとは

スレッド(thread) あるいは、 軽量プロセス(lightweight processes) とは、 1つの保護の単位としてのプロセス(タスク,あるいは,アドレス空間)内部にふくまれている論理的な並列処理の単位。

1つのプロセスの中に複数のスレッドを作って動作させることを マルチスレッド という。これに対して,今までのプロセスは,1つのアドレス空間に1つのスレッドだけが動いているもので、これを強調する時には, シングルスレッド のプロセスという。

シングルスレッドのプログラム
1度に1つの手続き(Cの関数)しか動かない。
マルチスレッドのプログラム
1度にスレッドの数だけの手続きが論理的には同時に動く。 (同期でブロックされているものも含む)

図? シングルスレッドのプロセスとマルチスレッドのプロセス

図? シングルスレッドのプロセスとマルチスレッドのプロセス

◆スレッドの利用目的

もともとスレッドは、 並列処理(parallel processing)を行う時に使われてきた(共有メモリ型マルチプロセッサ、Mach)。最近では、もともと内部に並列性を含んでいたプログラムを 自然に表現するためにも使われる。

◆スレッドの内容

個々のスレッドごとに持つもの プロセス全体で共有

■Pthread

Pthread は、POSIX 1003.1c-1995という標準に準拠したスレッド・ライブラリ。 POSIX Thread とも呼ばれる。

■Pthreadを利用したプログラムのコンパイル

Pthread を利用したプログラムを書く時には、次のようなヘッダ・ファイルを読み込む。

#include <pthread.h>

O2 でのコンパイルとリンク。-lpthread を付ける。

----------------------------------------------------------------------
% cc -o create create.c -lpthread [←]
% ./create [←]
...
% []
----------------------------------------------------------------------

■スレッドの生成・消滅

スレッドは、普通のプログラムの、 サブルーチン(C言語の関数、手続き) に近い。 サブルーチンの場合,呼び出すと、呼び出された方が動き、自分自身は,止まる。 スレッドでは,新たにスレッドを生成した場合, 生成した方と生成された方は,論理的には2つとも同時に動く。

■fork-joinモデル

図? fork-joinモデルの実現

図? fork-joinモデルの実現

  1. 逐次処理(スレッド/プロセスが1つ)の状態から始まる
  2. 並列性が必要になった時、fork命令で複数のスレッド/プロセスに分かれて並列処理を行う。
  3. 並列に動作できる部分が終ると join 命令で再び逐次処理に戻る。

◆Unixのfork

fork() システムコールでコピーが作られる。 join の代わりに、子どもは exit()、親は wait()。

◆Pthreadはcreate

Pthread では、コピーではなく create で新たにスレッドを作る。同じ関数を実行したい時には、直接 call する。(別の関数を実行するなら 呼ばなくてもよい。) 子スレッドでは、pthread_exit() (トップの手続きからリターン)、親は、pthread_join() する。

後で join する必要がない時には、pthread_detach() を使って切り離す。 (joinしなくてもゾンビが残らない。)

◆例

図? スレッドの生成とjoin

----------------------------------------------------------------------
   1:
   2:   /*
   3:           create-join-2.c -- スレッドを2つ作るプログラム
   4:           このファイルは次の場所にあります。
   5:                   ~yas/dsys/pthread/create-join-2.c
   6:           cp コマンドでコピーできます。
   7:           $Header: /home/lab2/OS/yas/dsys-1998/pthread/RCS/create-join-2.c,v 1.1 1999/01/18 11:52:49 yas Exp $
   8:           Start: 1999/01/18 20:49:16
   9:   */
  10:
  11:   #include <pthread.h>
  12:
  13:   void func1( int x );
  14:   void func2( int x );
  15:
  16:   main() {
  17:       pthread_t t1 ;
  18:       pthread_t t2 ;
  19:           pthread_create( &t1, NULL, (void *)func1, (void *)1 );
  20:           pthread_create( &t2, NULL, (void *)func2, (void *)2 );
  21:           printf("main()\n");
  22:           pthread_join( t1, NULL );
  23:           pthread_join( t2, NULL );
  24:   }
  25:
  26:   void func1( int x ) {
  27:       int i ;
  28:           for( i = 0 ; i<3 ; i++ ) {
  29:               printf("func1( %d ): %d \n",x, i );
  30:           }
  31:   }
  32:
  33:   void func2( int x ) {
  34:       int i ;
  35:           for( i = 0 ; i<3 ; i++ ) {
  36:               printf("func2( %d ): %d \n",x, i );
  37:           }
  38:   }
----------------------------------------------------------------------

実行例。
----------------------------------------------------------------------
% cp ~yas/dsys/pthread/create-join-2.c . [←]
% cc -o create-join-2 create-join-2.c -lpthread [←]
% ./create-join-2  [←]
main()
func1( 1 ): 0 
func2( 2 ): 0 
func1( 1 ): 1 
func2( 2 ): 1 
func1( 1 ): 2 
func2( 2 ): 2 
% []
----------------------------------------------------------------------
この例では、次の3つのスレッドが作られる。
  1. main を実行指定るスレッド
  2. func2 から作られたスレッド t2
  3. func1 から作られたスレッド t1
マルチスレッド・プログラミングでは、main関数もまた1つのスレッドが実行していると考える。これを 初期スレッド 、あるいは、 メインスレッド とよぶ。Pthrad では、メインスレッド以外のスレッドは、 pthread_create() により作られる。

どういう順序で実行されるかは、決まっていない。 決まっていない。スレッドは、もともと順番を決めないような処理、 非同期的(asynchronous) な処理を表現するためのもの。どうしても他のスレッドと同期を行なう必要が出てきた時には、mutex や条件変数といった同期機能を使う。

pthread_create()で指定された関数からリターンすると、そのスレッドが終了する。pthread_exit() を呼び出してもよい。ただし、 初期スレッド が終了すると、プロセス全体が終了する。 exit() システムコールを呼び出しても終了する。

■echo-server-fork(forkを使ったプログラム)

システムプログラムI(1学期)1998年6月9日dnl 1998年6月9日の再掲。

TCP/IP のポート番号 7 (echo) では、受け取ったデータをそのまま返すサービスを提供している。以下は、これと同じような機能を提供するサーバである。 複数の接続先(クライアント) の要求を同時に処理するために、クライアントごとに fork() システム・コールで専用の子プロセスを作っている。

----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        echo-server-fork.c -- 受け取った文字列をそのまま返すサーバ(fork版)
   4:	        ~yas/syspro1/ipc/echo-server-fork.c
   5:	        $Header: /home/lab2/OS/yas/syspro1-1998/ipc/RCS/echo-server-fork.c,v 1.5 1997/06/09 21:28:27 yas Exp $
   6:	        Start: 1997/06/09 19:46:40
   7:	*/
   8:	#include <stdio.h>
   9:	#include <sys/types.h>  /* socket(), time() */
  10:	#include <sys/socket.h> /* socket() */
  11:	#include <netinet/in.h> /* struct sockaddr_in */
  12:	
  13:	extern  void echo_server( int portno );
  14:	extern  void echo_reply( int com );
  15:	extern  void print_host_port( int portno );
  16:	extern  void tcp_peeraddr_print( int com );
  17:	extern  tcp_acc_port( int portno );
  18:	extern  int writen( int fd, char *buf, int nbytes );
  19:	
  20:	main( int argc, char *argv[] )
  21:	{
  22:	    int portno ;
  23:	        if( argc >= 3 )
  24:	        {
  25:	            fprintf( stdout,"Usage: %s host port\n",argv[0] );
  26:	            exit( -1 );
  27:	        }
  28:	        if( argc == 2 )
  29:	            portno = atoi( argv[1] );
  30:	        else
  31:	            portno = getuid();
  32:	        echo_server( portno );
  33:	}
  34:	
  35:	void echo_server( int portno )
  36:	{
  37:	    int acc,com ;
  38:	    pid_t child_pid ;
  39:	        acc = tcp_acc_port( portno );
  40:	        if( acc<0 )
  41:	            exit( -1 );
  42:	        print_host_port( portno );
  43:	        while( 1 )
  44:	        {
  45:	            if( (com = accept( acc,0,0 )) < 0 )
  46:	            {
  47:	                perror("accept");
  48:	                exit( -1 );
  49:	            }
  50:	            tcp_peeraddr_print( com );
  51:	            if( (child_pid=fork()) > 0 ) /* parent */
  52:	            {
  53:	                close( com );
  54:	            }
  55:	            else if( child_pid == 0 )
  56:	            {
  57:	                close( acc );
  58:	                echo_reply( com );
  59:	                printf("[%d,%d] connection closed.\n",getpid(),com );
  60:	                close( com );
  61:	                exit( 0 );
  62:	            }
  63:	        }
  64:	}
  65:	
  66:	void echo_reply( int com )
  67:	{
  68:	    char buf[BUFSIZ] ;
  69:	    int rcount ;
  70:	    int wcount ;
  71:	
  72:	        while( (rcount=read(com,buf,BUFSIZ)) > 0 )
  73:	        {
  74:	            if( (wcount=writen(com,buf,rcount))!= rcount )
  75:	            {
  76:	                 perror("write");
  77:	                 exit( 1 );
  78:	            }
  79:	            printf("[%d,%d] ",getpid(),com );
  80:	            fflush( stdout );
  81:	            write( 1, buf, rcount );
  82:	        }
  83:	}
<以下省略>
  85:	void print_host_port( int portno )
...
  93:	void tcp_peeraddr_print( int com )
...
 112:	tcp_acc_port( int portno )
...
 141:	int writen( int fd, char *buf, int nbytes )
----------------------------------------------------------------------

実行例。

サーバ側。サーバは、終了しないので、最後に、^CDel を押して、割り込みを掛けて終了させる。

----------------------------------------------------------------------
% ./echo-server-fork  [←]
run telnet adonis1 1231 
[4535,4] connection from 130.158.86.1:2368
[4539,4] 012
[4535,4] connection from 130.158.86.9:15629
[4542,4] abc
[4542,4] def
[4542,4] connection closed.
[4539,4] 345
[4539,4] connection closed.
^C
% []
----------------------------------------------------------------------
クライアント側(その1)。
----------------------------------------------------------------------
% telnet adonis1 1231 [←]
Trying 130.158.86.1...
Connected to adonis1.
Escape character is '^]'.
012[←]
012
345[←]
345
^]
telnet> quit[←]
Connection closed.
% []
----------------------------------------------------------------------
クライアント側(その2)。
----------------------------------------------------------------------
% % telnet adonis1 1231 [←]
Trying 130.158.86.1...
Connected to adonis1.
Escape character is '^]'.
abc[←]
abc
def[←]
def
^]
telnet> quit[←]
Connection closed.
% []
----------------------------------------------------------------------

■echo-server-pthread(pthreadを使ったプログラム)

fork() の代わりに、Pthread() を使う。
----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        echo-server-fork.c -- 受け取った文字列をそのまま返すサーバ(pthread版)
   4:	        ~yas/syspro1/ipc/echo-server-fork.c
   5:	        $Header: /home/lab2/OS/yas/dsys-1998/pthread/RCS/echo-server-pthread.c,v 1.1 1999/01/18 12:29:11 yas Exp $
   6:	        <-- ~yas/syspro1-1998/ipc/RCS/echo-server-fork.c,v 1.5 1997/06/09 21:28:27 yas Exp $
   7:	        Start: 1997/06/09 19:46:40
   8:	*/
   9:	#include <stdio.h>
  10:	#include <sys/types.h>  /* socket(), time() */
  11:	#include <sys/socket.h> /* socket() */
  12:	#include <netinet/in.h> /* struct sockaddr_in */
  13:	#include <pthread.h>
  14:	
  15:	extern  void echo_server( int portno );
  16:	extern  void echo_reply( int com );
  17:	extern  void print_host_port( int portno );
  18:	extern  void tcp_peeraddr_print( int com );
  19:	extern  tcp_acc_port( int portno );
  20:	extern  int writen( int fd, char *buf, int nbytes );
  21:	
  22:	main( int argc, char *argv[] )
  23:	{
  24:	    int portno ;
  25:	        if( argc >= 3 )
  26:	        {
  27:	            fprintf( stdout,"Usage: %s host port\n",argv[0] );
  28:	            exit( -1 );
  29:	        }
  30:	        if( argc == 2 )
  31:	            portno = atoi( argv[1] );
  32:	        else
  33:	            portno = getuid();
  34:	        echo_server( portno );
  35:	}
  36:	
  37:	void echo_server( int portno )
  38:	{
  39:	    int acc,com ;
  40:	    pthread_t worker ;
  41:	        acc = tcp_acc_port( portno );
  42:	        if( acc<0 )
  43:	            exit( -1 );
  44:	        print_host_port( portno );
  45:	        while( 1 )
  46:	        {
  47:	            if( (com = accept( acc,0,0 )) < 0 )
  48:	            {
  49:	                perror("accept");
  50:	                exit( -1 );
  51:	            }
  52:	            tcp_peeraddr_print( com );
  53:	            if( pthread_create( &worker, NULL, (void *)echo_reply, (void *)com)
  54:	                != 0 )
  55:	            {
  56:	                perror("pthread_create()");
  57:	                exit( -1 );
  58:	            }
  59:	            pthread_detach( worker );
  60:	        }
  61:	}
  62:	
  63:	void echo_reply( int com )
  64:	{
  65:	    char buf[BUFSIZ] ;
  66:	    int rcount ;
  67:	    int wcount ;
  68:	
  69:	        while( (rcount=read(com,buf,BUFSIZ)) > 0 )
  70:	        {
  71:	            if( (wcount=writen(com,buf,rcount))!= rcount )
  72:	            {
  73:	                 perror("write");
  74:	                 exit( 1 );
  75:	            }
  76:	            printf("[%d,%d,%d] ",getpid(),pthread_self(),com );
  77:	            fflush( stdout );
  78:	            write( 1, buf, rcount );
  79:	        }
  80:	        printf("[%d,%d,%d] connection closed.\n",getpid(),pthread_self(),com );
  81:	        close( com );
  82:	}
<以下省略>
  84:	void print_host_port( int portno )
...
  92:	void tcp_peeraddr_print( int com )
...
 112:	tcp_acc_port( int portno )
...
 141:	int writen( int fd, char *buf, int nbytes )
----------------------------------------------------------------------

実行例。

サーバ側。サーバは、終了しないので、最後に、^CDel を押して、割り込みを掛けて終了させる。

----------------------------------------------------------------------
% ./echo-server-pthread [←]
run telnet adonis1 1231 
[4509,65536,4] connection from 130.158.86.1:2367
[4509,65537,4] 012
[4509,65536,5] connection from 130.158.86.9:15628
[4509,65538,5] abc
[4509,65538,5] def
[4509,65538,5] connection closed.
[4509,65537,4] 345
[4509,65537,4] connection closed.
^C
% []
----------------------------------------------------------------------
クライアント側(その1)。
----------------------------------------------------------------------
% telnet adonis1 1231 [←]
Trying 130.158.86.1...
Connected to adonis1.
Escape character is '^]'.
012[←]
012
345[←]
345
^]
telnet> quit[←]
Connection closed.
% []
----------------------------------------------------------------------
クライアント側(その2)。
----------------------------------------------------------------------
% % telnet adonis1 1231 [←]
Trying 130.158.86.1...
Connected to adonis1.
Escape character is '^]'.
abc[←]
abc
def[←]
def
^]
telnet> quit[←]
Connection closed.
% []
----------------------------------------------------------------------

[SunRPC] [NFS] [Pthread]
↑[もどる] ・[1月19日] →[1月26日]
Last updated: 1999/01/26 03:34:20
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>