1 はじめに スレッド技術は 1960 年代にすでに提案されていましたが、マルチスレッドがオペレーティング システムに本格的に適用されたのは 1980 年代半ばになってからであり、この点では Solaris が先駆者でした。従来の Unix もスレッドの概念をサポートしていますが、プロセス内で許可されるスレッドは 1 つだけなので、マルチスレッドはマルチプロセスを意味します。現在、マルチスレッド テクノロジは、Windows/NT、そしてもちろん Linux を含む多くのオペレーティング システムでサポートされています。 2 シンプルなマルチスレッドプログラミング Linux システムでのマルチスレッドは、pthread と呼ばれる POSIX スレッド インターフェイスに従います。 Linux でマルチスレッド プログラムを作成するには、リンク時にヘッダー ファイル pthread.h とライブラリ libpthread.a を使用する必要があります。ちなみに、Linux での pthread の実装は、システム コール clone() を通じて実現されます。 clone() は Linux 固有のシステムコールです。使い方は fork と似ています。clone() の詳細については、関連するドキュメントを参照してください。以下に、最も単純なマルチスレッド プログラム example1.c を示します。 /* 例.c */ #include <stdio.h> #include <pthread.h> 空スレッド(void) { 整数 i; (i=0;i<3;i++) の場合 printf("これはpthreadです。/n"); } int メイン(void) { pthread_t ID; int i,ret; ret = pthread_create(&id,NULL,(void *) スレッド,NULL); if(ret!=0) { printf ("pthread 作成エラー!/n"); 出口(1) } (i=0;i<3;i++) の場合 printf("これがメインプロセスです。/n"); pthread_join(id,NULL); 戻り値 (0); } このプログラムをコンパイルします:
再度実行すると、次の結果が得られます。
2 つの結果は異なりますが、これは 2 つのスレッドが CPU リソースを競合した結果です。上記の例では、pthread_create と pthread_join の 2 つの関数を使用し、pthread_t 型の変数を宣言しました。 3 スレッドプロパティを変更する 前のセクションの例では、pthread_create 関数を使用してスレッドを作成しました。このスレッドでは、デフォルトのパラメータを使用しました。つまり、関数の 2 番目のパラメータは NULL に設定されました。実際、ほとんどのプログラムではデフォルトのプロパティを使用するだけで十分ですが、それでもスレッドの関連するプロパティを理解することは必要です。 #include <pthread.h> pthread_attr_t 属性; pthread_t の tid です。 /* プロパティ値を初期化し、すべてデフォルト値に設定します */ pthread_attr_init(&attr); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); pthread_create(&tid, &attr, (void *) my_function, NULL); スレッドの分離状態によって、スレッド自体が終了する方法が決まります。上記の例では、スレッドのデフォルト プロパティ (非デタッチ状態) を使用しています。この場合、元のスレッドは作成されたスレッドが終了するまで待機します。 pthread_join() 関数が返された場合にのみ、作成されたスレッドは終了し、占有していたシステム リソースを解放できます。デタッチされたスレッドの場合はそうではありません。他のスレッドによって待機されることはありません。自身の実行が完了すると、スレッドは終了し、システム リソースは直ちに解放されます。プログラマーは、ニーズに応じて適切な分離状態を選択する必要があります。スレッドのデタッチ状態を設定する関数は、pthread_attr_setdetachstate (pthread_attr_t *attr, int detachstate) です。 2 番目のパラメータは、PTHREAD_CREATE_DETACHED (デタッチされたスレッド) と PTHREAD _CREATE_JOINABLE (デタッチされていないスレッド) になります。ここで注意すべき点は、スレッドが別のスレッドとして設定され、このスレッドが非常に高速に実行されている場合、pthread_create 関数が戻る前に終了する可能性があるということです。終了した後、スレッド番号とシステム リソースが他のスレッドに転送されて使用される可能性があります。このようにして、pthread_create を呼び出すスレッドは間違ったスレッド番号を取得します。この状況を回避するには、特定の同期対策を講じることができます。最も簡単な方法の 1 つは、作成されたスレッドで pthread_cond_timewait 関数を呼び出して、スレッドをしばらく待機させ、pthread_create 関数が戻るのに十分な時間を残すことです。待機時間を設定することは、マルチスレッド プログラミングでよく使用される方法です。ただし、プロセス全体をスリープ状態にし、スレッド同期の問題を解決できない wait() などの関数は使用しないように注意してください。 /* 例.c */ #include <stdio.h> #include <pthread.h> 空スレッド(void) { 整数 i; (i=0;i<3;i++) の場合 printf("これはpthreadです。/n"); } int メイン(void) { pthread_t ID; int i,ret; ret = pthread_create(&id,NULL,(void *) スレッド,NULL); if(ret!=0) { printf ("pthread 作成エラー!/n"); 出口(1) } (i=0;i<3;i++) の場合 printf("これがメインプロセスです。/n"); pthread_join(id,NULL); 戻り値 (0); } 4スレッドデータ処理 プロセスと比較した場合、スレッドの最大の利点の 1 つはデータの共有です。各プロセスは親プロセスから継承したデータ セグメントを共有し、データを簡単に取得および変更できます。しかし、これによってマルチスレッド プログラミングに多くの問題も生じます。複数の異なるプロセスが同じ変数にアクセスすることに注意する必要があります。多くの関数は再入可能ではありません。つまり、関数の複数のコピーを同時に実行することはできません (異なるデータ セグメントが使用されない限り)。関数内で宣言された静的変数は問題を引き起こすことが多く、関数の戻り値も問題を引き起こす可能性があります。関数内で静的に宣言された領域のアドレスが返される場合、スレッドが関数を呼び出してアドレスを取得し、そのアドレスが指すデータを使用すると、別のスレッドがこの関数を呼び出してこのデータを変更する可能性があるからです。プロセス内で共有される変数は、最適化中にコンパイラが変数の使用方法を変更するのを防ぐために、キーワード volatile を使用して定義する必要があります (gcc で -OX パラメータを使用するなど)。変数を保護するには、セマフォ、ミューテックス、その他の方法を使用して、変数が正しく使用されるようにする必要があります。次に、スレッドデータの処理に関する関連知識を徐々に紹介します。 4.1 スレッドデータ シングルスレッド プログラムには、グローバル変数とローカル変数という 2 つの基本的なデータ タイプがあります。しかし、マルチスレッド プログラムには、スレッド データ (TSD: スレッド固有データ) という 3 番目のデータ型があります。これはグローバル変数と非常によく似ています。スレッド内では、各関数はグローバル変数のようにこれを呼び出すことができますが、スレッド外の他のスレッドからは見えません。このようなデータの必要性は明らかです。たとえば、共通変数 errno は標準エラー情報を返します。ほとんどすべての関数がこれを呼び出すことができるはずなので、これは明らかにローカル変数にはできません。しかし、これはグローバル変数にもできません。そうしないと、スレッド A で出力されるエラー メッセージがスレッド B のエラー メッセージと同じになる可能性が高くなります。このような変数を実装するには、スレッド データを使用する必要があります。各スレッドデータにキーを作成し、このキーに関連付けます。各スレッドでは、このキーを使用してスレッドデータを参照しますが、異なるスレッドでは、このキーで表されるデータは異なります。同じスレッドでは、同じデータ内容を表します。 /* 例.c */ #include <stdio.h> #include <pthread.h> 空スレッド(void) { 整数 i; (i=0;i<3;i++) の場合 printf("これはpthreadです。/n"); } int メイン(void) { pthread_t ID; int i,ret; ret = pthread_create(&id,NULL,(void *) スレッド,NULL); if(ret!=0) { printf ("pthread 作成エラー!/n"); 出口(1) } (i=0;i<3;i++) の場合 printf("これがメインプロセスです。/n"); pthread_join(id,NULL); 戻り値 (0); } このように、異なるスレッドで createMyWin 関数を呼び出すと、スレッド内で表示されるウィンドウ変数を取得できます。この変数は、pthread_getspecific 関数を通じて取得されます。上記の例では、関数 pthread_setspecific を使用してスレッド データをキーにバインドしています。これら 2 つの関数のプロトタイプは次のとおりです。 4.2 ミューテックスロック ミューテックス ロックは、一度に 1 つのスレッドだけがコードを実行するようにするために使用されます。その必要性は明らかです。各スレッドが同じファイルにデータを順番に書き込むと、最終結果は悲惨なものになります。 void リーダー関数 ( void ); void ライター関数 ( void ); char バッファ; int buffer_has_item = 0; pthread_mutex_t ミューテックス; 構造体timespec遅延; void main ( void ) { pthread_t リーダー; /* 遅延時間を定義する */ 遅延.tv_sec = 2; 遅延.tv_nec = 0; /* デフォルトのプロパティでミューテックスオブジェクトを初期化します */ pthread_mutex_init (&mutex,NULL); pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL); ライター関数(); } void ライター関数 (void){ (1){ /* ミューテックスをロックする */ pthread_mutex_lock (&mutex); (バッファにアイテムが0ある場合){ バッファ = make_new_item(); バッファにはアイテムが 1 つあります。 } /* ミューテックスを開く */ pthread_mutex_unlock(&mutex); pthread_delay_np(&遅延); } } void リーダー関数(void){ (1){ pthread_mutex_lock(&mutex); if(buffer_has_item==1){ バッファ内のアイテムを消費します。 バッファにアイテムがあります=0; } pthread_mutex_unlock(&mutex); pthread_delay_np(&遅延); } } ミューテックス変数 mutex はここで宣言されます。構造体 pthread_mutex_t は、システムによって割り当てられた属性オブジェクトを含むプライベート データ型です。関数 pthread_mutex_init は、ミューテックス ロックを生成するために使用されます。 NULL パラメータは、デフォルトのプロパティが使用されることを示します。特定の属性を持つミューテックスを宣言する必要がある場合は、関数 pthread_mutexattr_init を呼び出す必要があります。関数 pthread_mutexattr_setpshared および pthread_mutexattr_settype は、ミューテックス属性を設定するために使用されます。前の関数は、PTHREAD_PROCESS_PRIVATE と PTHREAD_PROCESS_SHARED の 2 つの値を持つプロパティ pshared を設定します。前者は異なるプロセス内のスレッドを同期するために使用され、後者は同じプロセス内の異なるスレッドを同期するために使用されます。上記の例では、デフォルトのプロパティ PTHREAD_PROCESS_PRIVATE を使用しました。後者は、ミューテックス ロック タイプを設定するために使用されます。オプションのタイプは、PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE、および PTHREAD _MUTEX_DEFAULT です。これらはそれぞれ異なるリストおよびロック解除メカニズムを定義します。通常は、最後のデフォルト属性が選択されます。 4.3 条件変数 前のセクションでは、ミューテックス ロックを使用してスレッド間のデータ共有と通信を実装する方法について説明しました。ミューテックス ロックの明らかな欠点は、ロック状態とロック解除状態の 2 つの状態しかないことです。条件変数は、スレッドをブロックして別のスレッドが信号を送信するのを待つことで、ミューテックス ロックの欠点を補います。ミューテックス ロックと一緒に使用されることがよくあります。条件変数は、スレッドをブロックするために使用されます。条件が満たされない場合、スレッドは対応するミューテックスのロックを解除し、条件が変化するのを待機することがよくあります。別のスレッドが条件変数を変更すると、対応する条件変数に通知され、この条件変数によってブロックされている 1 つ以上のスレッドが起動されます。これらのスレッドはミューテックスを再ロックし、条件が満たされているかどうかを再テストします。一般的に、条件変数はスレッドを同期するために使用されます。 pthread_mutex_t count_lock; pthread_cond_t カウントがゼロでない; 符号なしカウント; 減算カウント(){ pthread_mutex_lock (&count_lock); while(カウント==0) pthread_cond_wait(&count_nonzero, &count_lock); カウント=カウント -1; pthread_mutex_unlock (&count_lock); } 増分カウント(){ pthread_mutex_lock(&count_lock); カウント==0の場合 pthread_cond_signal(&count_nonzero); カウント=カウント+1; pthread_mutex_unlock(&count_lock); } カウント値が 0 の場合、減分関数は pthread_cond_wait でブロックされ、ミューテックス count_lock が開かれます。このとき、increment_count 関数が呼び出されると、pthread_cond_signal() 関数は条件変数を変更し、decrement_count() にブロックを停止するように指示します。読者は、2 つのスレッドでこれら 2 つの関数を別々に実行して、どのような結果が表示されるかを確認できます。 4.4 セマフォ セマフォは、本質的には共通リソースへのアクセスを制御するために使用される非負の整数カウンターです。共通リソースが増加すると、関数 sem_post() が呼び出され、セマフォが増加します。パブリック リソースは、セマフォ値が 0 より大きい場合にのみ使用できます。使用後は、関数 sem_wait() によってセマフォが削減されます。関数 sem_trywait() は関数 pthread_mutex_trylock() と同じ機能を持ち、関数 sem_wait() の非ブロッキング バージョンです。以下では、セマフォに関連する関数を 1 つずつ紹介します。これらはすべて、ヘッダー ファイル /usr/include/semaphore.h で定義されています。 /* ファイル sem.c */ #include <stdio.h> #include <pthread.h> #include <セマフォ.h> #定義 MAXSTACK 100 整数スタック[MAXSTACK][2]; 整数サイズ=0; sem_t sem; /* ファイル 1.dat からデータを読み取ります。データが読み取られるたびに、セマフォが 1 つ増加します */ void ReadData1(void){ ファイル *fp=fopen("1.dat","r"); while(!feof(fp)){ fscanf(fp,"%d %d",&stack[サイズ][0],&stack[サイズ][1]); sem_post(&sem); ++サイズ; } fp を閉じる } /*ファイル2.datからデータを読み込む*/ void ReadData2(void){ ファイル *fp=fopen("2.dat","r"); while(!feof(fp)){ fscanf(fp,"%d %d",&stack[サイズ][0],&stack[サイズ][1]); sem_post(&sem); ++サイズ; } fp を閉じる } /*バッファ内のデータをブロックして待機します。データを読み取った後、スペースを解放して待機を続けます*/ void ハンドルデータ1(void){ (1){ sem_wait(&sem); printf("プラス:%d+%d=%d/n",stack[サイズ][0],stack[サイズ][1], スタック[サイズ][0]+スタック[サイズ][1]); - サイズ; } } void ハンドルデータ2(void){ (1){ sem_wait(&sem); printf("乗算:%d*%d=%d/n",スタック[サイズ][0],スタック[サイズ][1], スタック[サイズ][0]*スタック[サイズ][1]); - サイズ; } } int main(void){ スレッドt1、t2、t3、t4; sem_init(&sem,0,0); pthread_create(&t1,NULL,(void *)HandleData1,NULL); pthread_create(&t2,NULL,(void *)HandleData2,NULL); pthread_create(&t3,NULL,(void *)ReadData1,NULL); pthread_create(&t4,NULL,(void *)ReadData2,NULL); /* プログラムが途中で終了するのを防ぎ、ここで無期限に待機させます */ pthread_join(t1,NULL); } Linux では、gcc -lpthread sem.c -o sem コマンドを使用して実行可能ファイル sem を生成します。 事前にデータファイル 1.dat と 2.dat を編集してあります。その内容がそれぞれ 1 2 3 4 5 6 7 8 9 10 と -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 であると仮定して、 sem を実行すると次の結果が得られます。
ここから、スレッド間の競合関係がわかります。サイズの値は各スレッドによって任意に変更されるため、値が当初意図した順序で表示されません。これは、マルチスレッド プログラミングで注意が必要な問題でもあります。 5 まとめ マルチスレッド プログラミングは非常に興味深く、便利な技術です。マルチスレッド技術を使用する Network Ants は、最もよく使用されるダウンロード ツールの 1 つです。マルチスレッド技術を使用する Grep は、シングルスレッドの grep よりも数倍高速です。同様の例はたくさんあります。誰もがマルチスレッド技術を活用して、効率的で実用的なプログラムを作成できるようになることを願っています。 これは、Linuxの下でのマルチスレッドプログラミングの例の分析に関するこの記事の終わりです。 以下もご興味があるかもしれません:
|
<<: Vue el-date-picker 動的制限時間範囲ケースの詳細な説明
この記事では、explain を使用して SQL ステートメントを分析する方法を紹介します。実際、イ...
1. 実装のアイデアインターフェース署名の目的は、リクエストパラメータが改ざんされていないか、リクエ...
この記事では、主に同じ親タグの左側と右側にある 2 つのボタンの CSS レイアウト方法を紹介し、皆...
Navicat を使用してリモート Linux MySQL データベースに接続すると、不明なエラー ...
10.4.1 フレームセットとフレームの違い まず、フレームセットとフレームの違いについて説明します...
この記事では、MySQL無料インストール版(zip)のインストールと設定のチュートリアルを参考までに...
目次序文1. Iceraven ブラウザ (Firefox) (Android) 2. (アンドロイ...
この記事では、二次リンク効果を実現するためのReact+tsの具体的なコードを参考までに共有します。...
Mybatis ページングプラグイン pageHelper の詳細な説明と簡単な例動作フレームワーク...
CSS スタイル:コードをコピーコードは次のとおりです。 <スタイル タイプ="te...
このチュートリアルの動作環境: Windows 7 システム、vue 2.9.6 バージョン、DEL...
ローカル データベースに接続すると、Navicat for MySQL は以下のように 1045 エ...
Remote-SSHをインストールして設定するまず VSCode を開き、拡張機能を見つけて、Rem...
1. Canvas画像をCSS背景画像として使用するCSS ペイント API は、Canvas キャ...
0x0 テスト環境本社本番サーバーと支社バックアップサーバーはリモートデータバックアップが必要です...