何らかの理由で、メイン関数の最後でpthread_exit(NULL)
を呼び出すと、実行中のすべてのスレッド(少なくともメイン関数で作成されたもの)がmain
が終了する前に実行を終了することが保証されると思いました。ただし、2つのpthread_join
関数(main
の最後)を明示的に呼び出さずにこのコードを実行すると、セグメンテーション違反が発生します。これは、main
関数に2つのスレッドがジョブを終了する前に終了したため、char bufferは使用できなくなりました。ただし、これら2つのpthread_join
関数呼び出しをmain
の最後に含めると、正常に実行されます。実行中のすべてのスレッドが終了する前にmain
が終了しないことを保証するには、main
で直接初期化されたすべてのスレッドに対してpthread_join
を明示的に呼び出す必要がありますか?
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <semaphore.h>
#define NUM_CHAR 1024
#define BUFFER_SIZE 8
typedef struct {
pthread_mutex_t mutex;
sem_t full;
sem_t empty;
char* buffer;
} Context;
void *Reader(void* arg) {
Context* context = (Context*) arg;
for (int i = 0; i < NUM_CHAR; ++i) {
sem_wait(&context->full);
pthread_mutex_lock(&(context->mutex));
char c = context->buffer[i % BUFFER_SIZE];
pthread_mutex_unlock(&(context->mutex));
sem_post(&context->empty);
printf("%c", c);
}
printf("\n");
return NULL;
}
void *Writer(void* arg) {
Context* context = (Context*) arg;
for (int i = 0; i < NUM_CHAR; ++i) {
sem_wait(&context->empty);
pthread_mutex_lock(&(context->mutex));
context->buffer[i % BUFFER_SIZE] = 'a' + (Rand() % 26);
float ranFloat = (float) Rand() / Rand_MAX;
if (ranFloat < 0.5) sleep(0.2);
pthread_mutex_unlock(&(context->mutex));
sem_post(&context->full);
}
return NULL;
}
int main() {
char buffer[BUFFER_SIZE];
pthread_t reader, writer;
Context context;
srand(time(NULL));
int status = 0;
status = pthread_mutex_init(&context.mutex, NULL);
status = sem_init(&context.full,0,0);
status = sem_init(&context.empty,0, BUFFER_SIZE);
context.buffer = buffer;
status = pthread_create(&reader, NULL, Reader, &context);
status = pthread_create(&writer, NULL, Writer, &context);
pthread_join(reader,NULL); // This line seems to be necessary
pthread_join(writer,NULL); // This line seems to be necessary
pthread_exit(NULL);
return 0;
}
その場合、同じスレッド識別子を使用して(以下のコードのように)多数の同一のスレッドが作成される場合をどのように処理できますか?その場合、main
が終了する前にすべてのスレッドが終了していることを確認するにはどうすればよいですか?これを行うには、本当にNUM_STUDENTS pthread_t
識別子の配列を保持する必要がありますか? Studentスレッドにセマフォを通知させてから、main
関数にそのセマフォを待機させることでこれを実行できると思いますが、これを行う簡単な方法は本当にありませんか?
int main()
{
pthread_t thread;
for (int i = 0; i < NUM_STUDENTS; i++)
pthread_create(&thread,NULL,Student,NULL); // Threads
// Make sure that all student threads have finished
exit(0);
}
pthread_exit()
は、スレッドが自身の実行を終了するために呼び出す関数です。あなたが与えた状況では、それはあなたのメインプログラムスレッドから呼び出されるべきではありません。
ご存知のように、pthread_join()
は、main()
からの結合可能なスレッドの完了を待つ正しい手段です。
また、ご存知のとおり、pthread_create()
から返された値を維持してpthread_join()
に渡す必要があります。
これが意味するのは、pthread_join()
を使用する場合、作成するすべてのスレッドに同じpthread_t
変数を使用することはできないということです。
むしろ、pthread_t
の配列を作成して、各スレッドのIDのコピーを作成します。
メインスレッドが_pthread_exit
_を呼び出したときにプログラムを終了するかどうかは別として、_pthread_exit
_は言います
Pthread_exit()関数は、呼び出し元のスレッドを終了します
そしてまた:
スレッドが終了した後、スレッドのローカル(自動)変数へのアクセスの結果は未定義です。
コンテキストはmain()
の自動変数であるため、テストする対象をテストする前に、コードがフォールオーバーする可能性があります...
元のコードを実行している環境については言及していません。 nanosleep()
を使用するようにコードを変更しました(質問へのコメントで述べたように、sleep()
は整数を取り、したがってsleep(0.2)
はsleep(0)
)と同等であり、MacOS X10.6.4でプログラムをコンパイルしました。
正常に動作します。 0.5の確率係数で実行するのに約100秒かかり(予想どおり、実行時間を約10秒に短縮するために0.05に変更しました)、ランダムな文字列を生成しました。
何も得られないこともあれば、多くなることもあれば、データが少なくなることもありました。しかし、コアダンプは表示されませんでした(任意に大きなコアダンプを許可するための「ulimit-cunlimited」を使用しても)。
最終的に、いくつかのツールを適用して、常に1025文字(1024が生成されて改行)を取得することを確認しましたが、かなり頻繁に1024 ASCII NUL文字を取得しました。真ん中、時には最初など:
_$ ./pth | tpipe -s "vis | ww -w64" "wc -c"
1025
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000ocriexffwgdvdvyfitjtvlzcoffhusjo
zyacniffpsfswesgrkuxycsubufamxxzkrkqnwvsxcbmktodessyohixsmuhdovt
hhertqjjinzoptcuqzertybicrzaeyqlyublbfgutcdvftwkuvxhouiuduoqrftw
xjkgqutpryelzuaerpsbotwyskaflwofseibfqntecyseufqxvzikcyeeikjzsye
qxhjwrjmunntjwhohqovpwcktolcwrvmfvdfsmkvkrptjvslivbfjqpwgvroafzn
fkjumqxjbarelbrdijfrjbtiwnajeqgnobjbksulvcobjkzwwifpvpmpwyzpwiyi
cdpwalenxmocmtdluzouqemmjdktjtvfqwbityzmronwvulfizpizkiuzapftxay
obwsfajcicvcrrjehjeyzsngrwusbejiovaaatyzouktetcerqxjsdpswixjpege
blxscdebfsptxwvwsllvydipovzmnrvoiopmqotydqaujwdykidmwzitdsropguv
vudyfiaaaqueyllnwudfpplcfbsngqqeyucdawqxqzczuwsnaquofreilzvdwbjq
ksrouwltvaktpdrvjnqahpdqdshmmvntspglexggshqbjrvxceaqlfnukedxzlms
cnapdtgtcoyhnglojbjnplowericrzbfulvrobfn
$
_
(「tpipe」プログラムは「tee」に似ていますが、ファイルではなくパイプに書き込みます(「-s」オプションを指定しない限り、標準出力に書き込みます)。「vis」は、Kernighan&Pikeの「UNIXプログラミング環境」に由来します。 ; 'ww'は 'Wordラッパー'ですが、ここには単語がないため、ブルートフォースで幅64でラップします。)
私が見た動作は非常に不確定でした-実行ごとに異なる結果が得られました。ランダムな文字を順番にアルファベットに置き換えても( 'a' + i%26)、それでも奇妙な動作をしていました。
いくつかのデバッグ印刷コード(およびコンテックスへのカウンター)を追加しましたが、セマフォ_context->full
_がリーダーに対して適切に機能していないことは明らかでした-ライターが書き込む前に相互排除に入ることが許可されていました何でも。
ミューテックスとセマフォの操作にエラーチェックを追加すると、次のことがわかりました。
_sem_init(&context.full) failed (-1)
errno = 78 (Function not implemented)
_
したがって、奇妙な出力は、MacOSXがsem_init()
を実装していないためです。それは奇妙です; sem_wait()
関数がerrno = 9(EBADF '不正なファイル記述子')で失敗しました。最初にそこにチェックを追加しました。次に、初期化を確認しました...
sem_open()
呼び出しは成功し、見栄えがよくなります(名前は_"/full.sem"
_と_"/empty.sem"
_、フラグはO_CREAT、モード値は0444、0600、0700、異なる時間、初期値は0とBUFFER_SIZE、 sem_init()
と同様)。残念ながら、最初のsem_wait()
またはsem_post()
操作は、errno = 9(EBADF '不正なファイル記述子')で再び失敗します。
pthread_join()
呼び出しの動作なしではクラッシュしません。pthread_join()
は、他のスレッドが完了するのを待つ標準的な方法です。私はそれに固執します。
または、スレッドカウンターを作成し、すべての子スレッドで開始時に1ずつインクリメントし、終了時に1ずつデクリメントして(もちろん適切にロックして)、main()
にこのカウンターを待機させることもできます。 0をヒットします。(pthread_cond_wait()
が私の選択です)。
Context
とbuffer
が静的ストレージ期間で宣言されている場合(Steve Jessop、caf、David Schwartzがすでに指摘しているように)、pthread_join(reader,NULL);
を呼び出す必要はまったくありません。
Context
およびbuffer
staticを宣言すると、_Context *context
_をそれぞれ_Context *contextr
_または_Context *contextw
_に変更する必要もあります。
さらに、_pthread_exit.c
_と呼ばれる次の書き直しは、sem_init()
をsem_open()
に置き換え、nanosleep()
を使用します(Jonathan Lefflerによって提案されています)。
_pthread_exit
_はMac OS X 10.6.8でテストされ、ASCII NUL文字を出力しませんでした。
_/*
cat pthread_exit.c (sample code to test pthread_exit() in main())
source:
"pthreads in C - pthread_exit",
http://stackoverflow.com/questions/3330048/pthreads-in-c-pthread-exit
compiled on Mac OS X 10.6.8 with:
gcc -ansi -pedantic -std=gnu99 -Os -Wall -Wextra -Wshadow -Wpointer-arith -Wcast-qual -Wstrict-prototypes \
-Wmissing-prototypes -Wformat=2 -l pthread -o pthread_exit pthread_exit.c
test with:
time -p bash -c './pthread_exit | tee >(od -c 1>&2) | wc -c'
*/
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <semaphore.h>
#include <time.h>
void *Reader(void* arg);
void *Writer(void* arg);
// #define NUM_CHAR 1024
#define NUM_CHAR 100
#define BUFFER_SIZE 8
typedef struct {
pthread_mutex_t mutex;
sem_t *full;
sem_t *empty;
const char *semname1;
const char *semname2;
char* buffer;
} Context;
static char buffer[BUFFER_SIZE];
static Context context;
void *Reader(void* arg) {
Context *contextr = (Context*) arg;
for (int i = 0; i < NUM_CHAR; ++i) {
sem_wait(contextr->full);
pthread_mutex_lock(&(contextr->mutex));
char c = contextr->buffer[i % BUFFER_SIZE];
pthread_mutex_unlock(&(contextr->mutex));
sem_post(contextr->empty);
printf("%c", c);
}
printf("\n");
return NULL;
}
void *Writer(void* arg) {
Context *contextw = (Context*) arg;
for (int i = 0; i < NUM_CHAR; ++i) {
sem_wait(contextw->empty);
pthread_mutex_lock(&(contextw->mutex));
contextw->buffer[i % BUFFER_SIZE] = 'a' + (Rand() % 26);
float ranFloat = (float) Rand() / Rand_MAX;
//if (ranFloat < 0.5) sleep(0.2);
if (ranFloat < 0.5)
nanosleep((struct timespec[]){{0, 200000000L}}, NULL);
pthread_mutex_unlock(&(contextw->mutex));
sem_post(contextw->full);
}
return NULL;
}
int main(void) {
pthread_t reader, writer;
srand(time(NULL));
int status = 0;
status = pthread_mutex_init(&context.mutex, NULL);
context.semname1 = "Semaphore1";
context.semname2 = "Semaphore2";
context.full = sem_open(context.semname1, O_CREAT, 0777, 0);
if (context.full == SEM_FAILED)
{
fprintf(stderr, "%s\n", "ERROR creating semaphore semname1");
exit(EXIT_FAILURE);
}
context.empty = sem_open(context.semname2, O_CREAT, 0777, BUFFER_SIZE);
if (context.empty == SEM_FAILED)
{
fprintf(stderr, "%s\n", "ERROR creating semaphore semname2");
exit(EXIT_FAILURE);
}
context.buffer = buffer;
status = pthread_create(&reader, NULL, Reader, &context);
status = pthread_create(&writer, NULL, Writer, &context);
// pthread_join(reader,NULL); // This line seems to be necessary
// pthread_join(writer,NULL); // This line seems to be necessary
sem_unlink(context.semname1);
sem_unlink(context.semname2);
pthread_exit(NULL);
return 0;
}
_
pthread_join
は次のことを行います:
pthread_join()
関数は、ターゲットスレッドがすでに終了していない限り、ターゲットスレッドが終了するまで呼び出しスレッドの実行を一時停止します。 NULL以外のvalue_ptr
引数を使用して成功したpthread_join()
呼び出しから戻ると、終了スレッドによってpthread_exit()
に渡された値は、value_ptr
によって参照される場所で使用可能になります。pthread_join()
が正常に戻ると、ターゲットスレッドは終了しています。同じターゲットスレッドを指定するpthread_join()
への複数の同時呼び出しの結果は未定義です。pthread_join()
を呼び出すスレッドがキャンセルされた場合、ターゲットスレッドはデタッチされません。
ただし、exe
が終了しないようにする軽量ループを使用することで、同じことを実現できます。 Glibではこれは GMainLoop を作成することで実現され、Gtk +では gtk_main を使用できます。スレッドの完了後、メインループを終了するか、gtk_exit
を呼び出す必要があります。
または、ソケット、パイプ、および選択したシステムコールの組み合わせを使用して独自の待機機能を作成することもできますが、これは必須ではなく、練習用の演習と見なすことができます。
通常のpthread
セマンティクスに従って、たとえば ここ 、あなたの元のアイデアは確認されているようです:
Main()が作成したスレッドの前に終了し、pthread_exit()で終了した場合、他のスレッドは引き続き実行されます。それ以外の場合は、main()が終了すると自動的に終了します。
しかし、それがPOSIXスレッドstandardの一部なのか、それとも一般的ではあるが普遍的ではない「Nice tohave」アドオンの一口(I一部の実装がこの制約を尊重しないことを知っていますか?それにもかかわらず、それらの実装が標準準拠と見なされるかどうかはわかりません!-)。したがって、安全のために、終了する必要のあるすべてのスレッドの参加を推奨する慎重なコーラスに参加する必要があります-または、Jon Postel put it (TCP /のコンテキストで) IPの実装:
Be conservative in what you send; be liberal in what you accept.
tCP/IPだけでなく、もっと広く使われるべき「ロバストネスの原則」;-)。
pthread_exit(3)
それを呼び出すスレッドを終了します(ただし、他のスレッドがまだ実行されている場合は、プロセス全体ではありません)。あなたの例では、他のスレッドはmain
のスタックで変数を使用します。したがって、main
のスレッドが終了し、そのスタックが破棄されると、マップされていないメモリにアクセスするため、セグメンテーション違反が発生します。
他の人が提案した適切な pthread_join(3)
手法を使用するか、共有変数を静的ストレージに移動します。
スレッドに変数へのポインタを渡すときは、少なくともスレッドがその変数にアクセスしようとする限り、その変数の存続期間が長いことを確認する必要があります。スレッドポインタをbuffer
とcontext
に渡します。これらは、main
内のスタックに割り当てられます。 main
が終了するとすぐに、これらの変数は存在しなくなります。したがって、これらのスレッドがこれらのポインタにアクセスする必要がなくなったことを確認するまで、main
を終了することはできません。
95%の場合、この問題の修正は次の単純なパターンに従うことです。
1)パラメータを保持するオブジェクトを割り当てます。
2)オブジェクトにパラメータを入力します。
3)オブジェクトへのポインタを新しいスレッドに渡します。
4)新しいスレッドがオブジェクトの割り当てを解除できるようにします。
残念ながら、これは2つ以上のスレッドで共有されているオブジェクトではうまく機能しません。その場合、パラメータオブジェクト内に使用カウントとミューテックスを配置できます。各スレッドは、ミューテックスの保護の下で、使用回数を減らすことができます。使用カウントをゼロに落とすスレッドは、オブジェクトを解放します。
buffer
とcontext
の両方に対してこれを行う必要があります。使用回数を2
に設定し、このオブジェクトへのポインタを両方のスレッドに渡します。