C LANGUAGE TECHNOLOGY

【C言語】スレッドの生成と実行【pthread,マルチスレッド,スレッドIDの取得】

悩んでいる人

C言語でスレッドを教えて!

こういった悩みにお答えします.

本記事の信頼性

  • リアルタイムシステムの研究歴12年.
  • 東大教員の時に,英語でOSの授業.
  • 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校コンピュータサイエンス学部2021年の世界大学学術ランキングで20位)で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発
  • プログラミング歴15年以上,習得している言語: C/C++Solidity,Java,Python,Ruby,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
  • 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」GitHubにオープンソースとして公開

こういった私から学べます.

スレッド

スレッドとは,OSにおけるプログラムの実行単位です.

スレッドが管理する情報はプロセスより少ないので,スレッド間のコンテキストスイッチのオーバヘッドも小さいです.

プロセスと比較して,あるスレッドは同じプロセス内の他のスレッド同士でメモリ空間を共有するため,スレッド間のデータのやり取りのオーバヘッドが小さくできます.

同じプロセス内のスレッド同士でメモリ空間を共有するため,あるスレッドは同じプロセス内の他のスレッドのデータを変更すると,正常に動作しなくなることがあります.

なので,マルチスレッドプログラミングでは,スレッド間で共有するデータの読み書きに注意する必要があります.

プロセスの生成と実行を知りたいあなたはこちらからどうぞ.

C言語 プロセス
【C言語】プロセスの生成と実行【fork/wait/execve/getpid/getppid関数】

こういった悩みにお答えします. こういった私から学べます. 目次1 プロセス2 fork関数でプロセスの生成,wait関数で子プロセスの終了まで待機3 execve関数でコマンドの実行4 getpid ...

続きを見る

本記事では,x86(x64を含む)だけでなくARM 64ビット(AARCH64)用のコードがあります.

AARCH64用の開発環境の構築は以下の記事を参考にして下さい.

C言語 ラズパイ3
【C言語】Raspberry Pi 3のユーザモードとカーネルモードでプログラムの実行

こういった悩みにお答えします. こういった私から学べます. 目次1 Raspberry Pi 32 Raspberry Pi 3のエミュレータ「QEMU」3 Raspberry Pi 3でC言語のプロ ...

続きを見る

本記事ではPOSIXスレッドの実装であるpthreadを例にして紹介していきます.

pthreadによるマルチスレッドプログラミング

pthread_create関数は,呼び出し元のスレッドと並行して実行する新しいスレッドを生成する関数です.

新しいスレッドは,argを第1引数とするstart_routineという関数になります.

引数attrには,その新しいスレッドに適用するスレッド属性を指定します.

スレッド属性に関する詳細は,PTHREAD_ATTR_INITに記載されています.

成功すると新しく作成したスレッドの識別子が引数threadの指す領域へ格納され,関数の戻り値は0になります.

pthread_join関数は,呼び出し元のスレッドの実行を停止し,thで指定したスレッドが終了するのを待ちます.

thread_returnがNULLでない場合,thの戻り値がthread_returnで指し示される領域に格納されます.

pthreadでスレッドを生成して実行するコードは以下になります.

実行結果は以下になります.

pthreadのライブラリを利用する時は,-lpthreadオプションを付けてコンパイルすることに注意して下さい.

pthreadでスレッドIDの取得

pthreadでスレッドIDを取得する方法を紹介します.

pthread_self関数

pthread_self関数は,呼び出し元のスレッドのIDを取得する関数です.

ここで,pthread_self関数で取得するスレッドIDはLinuxが管理するスレッドIDではないことに注意して下さい.

ではpthread_self関数の戻り値が何かというと,生成したpthreadが利用するスタックのアドレスになります.

このスタックのアドレスの値は,スレッドが利用可能なメモリ領域の最大値もしくは近い値だと思われます.

Linuxが管理するスレッドIDとは,psコマンドを-Lオプション付きで実行した時に表示される軽量プロセス(LWP:Light-Weight Process)のIDのことです.

以下のpsコマンドの実行結果で,PIDがプロセスID,LWPがLinuxが管理するスレッドIDになります.

pthread_self関数を利用し,x86/ARMの命令でスタックアドレスを取得して表示するコードは以下になります.

17行目でx86のmov命令,19行目でARMのmov命令を利用していることに注意して下さい.

x86の実行結果は以下になります.

tid毎のpthread_create関数の第1引数とpthread_self関数の戻り値が同じアドレスになっています.

また,func_thread関数でスタックポインタを取得した値saは,上記のアドレスから0x840引いた値になっていることがわかります.

ARMの実行結果は以下になります.

x86と同様に,tid毎のpthread_create関数の第1引数とpthread_self関数の戻り値が同じアドレスになっています.

また,x86とは異なり,saは上記のアドレスから0x850引いた値になっていることがわかります.

Linux固有のgettid関数

Linux固有のgettid関数は,Linuxが管理するスレッドIDを取得する関数です.

こちらのスレッドIDはpsコマンドで-Lオプションを付けて実行すると表示されるLWPの値と同じになります.

gettid関数を利用するコードは以下になります.

実行結果は以下になります.

2行目の実行方法「a.out&; ps -L; fg」は少し複雑ですが,セミコロン「;」で区切って以下のようになります.

  • a.out&:実行ファイルをバックグラウンドのジョブとして実行
  • ps -L:psコマンドでPIDとLWPを表示
  • fg:バックグラウンドで実行しているものをフォアグラウンドのジョブとして実行(または再開)

4行目のgettid関数の戻り値と8行目のLWP,5行目のgettid関数の戻り値と9行目のLWPがそれぞれ同じ値であることがわかります.

参考:pthread_create関数の第1引数を利用して生成したスレッドのスレッドIDの取得

pthread_create関数の第1引数pthreadを利用して生成したスレッドのスレッドIDを取得する方法を紹介します.

生成したスレッドのスレッドIDは,x64の場合はpthread + 0x2d0,AARCH64の場合はpthread + 0xd0が指すアドレスにあります.

この実装は実験的ですので,LinuxカーネルのバージョンやGCCコンパイラのオプションによっては動作しない可能性があることに注意して下さい.

生成したスレッドのスレッドIDを取得するgettid_pthread関数を自作したコードは以下になります.

x64の実行結果は以下になります.

生成元のスレッドが呼び出しているgettid_pthread関数の戻り値と,生成したスレッドが呼び出しているfunc_thread関数内のgettid関数の戻り値が,同じスレッドID(11359)になっていることがわかります.

AARCH64の実行結果は以下になります.同様です.

まとめ

C言語でスレッドの生成と実行方法を紹介しました.

具体的には,pthreadによるマルチスレッドプログラミングとスレッドIDの取得方法を解説しました.

C言語を独学で習得することは難しいです.

私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.

友だち追加

独学が難しいあなたは,C言語を学べるおすすめのオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!

-C LANGUAGE, TECHNOLOGY
-, , , , , , , , ,