昔、再入可能性の問題について記事を書きました
実際に筆記試験を受けている場合、面接官は
次のコードの何が問題なのかと尋ねます。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
int NUM = 0;
void * PrintNum(void * ptr)
{
while(NUM++ < 200000000) {
}
printf("%s,NUM:%d\n", (char*)ptr, NUM);
NUM = 0;
}
int main(int argc,char ** argv)
{
pthread_t thread1 = -1;
char thread1_name[] = "thread2";
if(pthread_create(&thread1, NULL, PrintNum, thread1_name)!=0) {
printf("thread1 creat error\n");
}
pthread_t thread2 = -1;
char thread2_name[] = "thread2";
if(pthread_create(&thread1, NULL, PrintNum, thread2_name)!=0) {
printf("thread1 creat error\n");
}
void * result1;
void * result2;
pthread_join(thread1, &result1);
pthread_join(thread2, &result2);
}
このコードには次の問題があります。
2 番目のスレッドを作成するときに、間違った変数名が使用されました。
pthread_create(& thread1 , NULL, PrintNum, thread2_name) は
pthread_create(& thread2 , NULL, PrintNum, thread2_name)に変更する必要があります。
PrintNum 関数では、グローバル変数 NUM へのアクセスがスレッドセーフであることを保証するために、同期メカニズムは使用されません。複数のスレッドが NUM に同時にアクセスすると、データの不整合やプログラム エラーが発生する可能性があります。スレッドの安全性を確保するには、ミューテックスなどの同期メカニズムを使用する必要があります。
PrintNum 関数では、スレッドの実行時間をシミュレートするために無限ループが使用されます。これにより、スレッドが常に CPU リソースを占有し、システムのパフォーマンスに影響を与えます。一定時間待機し、スレッドをブロック状態にして、CPU リソースを解放するには、sleep 関数を使用する必要があります。
main関数では、スレッド作成・待ち合わせの戻り値をチェックしていないため、スレッド作成・待ち合わせに失敗する場合がありますので、戻り値を確認し、エラー処理を行ってください。
メイン関数では、スレッド リソースは解放されません。
スレッドの実行が完了した後、pthread_exit 関数または pthread_cancel 関数を呼び出して、スレッド リソースを解放する必要があります。
main 関数では、return ステートメントを使用してプログラムを終了するのではなく、プログラムの実行後に return ステートメントを使用してプログラムを終了する必要があります。
上記の回答は、chatgpt にコードを投げたときに得た応答です。
とはいえ、このコードを正しく実行するには、いくつかの変更が必要です。
コードを変更する
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
unsigned long int NUM = 0;
void * PrintNum(void * ptr)
{
unsigned long int count = 0;
while(NUM++ < 20000) {
count++;
}
printf("%s run %lu in NUM:%lu \n", (char*)ptr, count, NUM);
NUM = 0;
pthread_exit(NULL);
}
int main(int argc,char ** argv)
{
pthread_t thread1 = -1;
char thread1_name[] = "thread2";
if(pthread_create(&thread1, NULL, PrintNum, thread1_name)!=0) {
printf("thread1 creat error\n");
}
pthread_t thread2 = -1;
char thread2_name[] = "thread2";
if(pthread_create(&thread2, NULL, PrintNum, thread2_name)!=0) {
printf("thread1 creat error\n");
}
void * result1;
void * result2;
pthread_join(thread1, &result1);
pthread_join(thread2, &result2);
return 0;
}
実行関数の出力
thread2 .....run 18188 in NUM:20001
thread2 .....run 6812 in NUM:20001
計算では、2 つのスレッドによるグローバル変数の実行回数が異なることがわかります。
それから私はあらゆる種類のいじりをしました
最後まで、当初のアイデアを完成させなかった
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
unsigned long int g_num = 0;
unsigned long int count_thread1 = 0, count_thread2 = 0;
#define RUN_COUNT 2000
pthread_mutex_t mutex;
pthread_cond_t cond;
int cond_flag = 1;
void * print_num(void * ptr)
{
while(1) {
pthread_mutex_lock(&mutex);
if (g_num >= RUN_COUNT) {
pthread_mutex_unlock(&mutex);
break;
}
if (strcmp((char*)ptr, "thread1") == 0) {
if (cond_flag != 1) {
pthread_cond_wait(&cond, &mutex);
}
} else if (strcmp((char*)ptr, "thread2") == 0) {
if (cond_flag != 2) {
pthread_cond_wait(&cond, &mutex);
}
}
if (strcmp((char*)ptr, "thread1") == 0) {
count_thread1++;
cond_flag = 2;
} else if (strcmp((char*)ptr, "thread2") == 0) {
count_thread2++;
cond_flag = 1;
}
g_num++;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
if (strcmp((char*)ptr, "thread1") == 0) {
printf("%s run %lu in g_num:%lu \n", (char*)ptr, count_thread1, g_num);
} else if (strcmp((char*)ptr, "thread2") == 0) {
printf("%s run %lu in g_num:%lu \n", (char*)ptr, count_thread2, g_num);
}
g_num = 0;
printf("%s run over \n", (char*)ptr);
pthread_exit(NULL);
}
int main(int argc,char ** argv)
{
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_t thread1 = -1;
char thread1_name[] = "thread1";
if(pthread_create(&thread1, NULL, print_num, thread1_name)!=0) {
printf("thread1 creat errorn");
return -1;
}
pthread_t thread2 = -1;
char thread2_name[] = "thread2";
if(pthread_create(&thread2, NULL, print_num, thread2_name)!=0) {
printf("thread1 creat errorn");
return -1;
}
while (1) {
if (g_num > RUN_COUNT) {
if (count_thread1 < RUN_COUNT/2) {
cond_flag = 1;
pthread_cond_signal(&cond);
} else if (count_thread2 < RUN_COUNT/2) {
cond_flag = 2;
pthread_cond_signal(&cond);
} else {
break;
}
}
}
void * result1;
void * result2;
pthread_join(thread1, &result1);
pthread_join(thread2, &result2);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
コード出力
thread2 run 1000 in g_num:2000
thread2 run over
thread1 run 1001 in g_num:2001
thread1 run over
上記の 2 つのコード セクションでは、最初のセクションはクリティカル セクションを保護していません.2 つのスレッドがスケジュールされている場合、クリティカル セクションへのアクセスもランダムになります。
2 番目の段落では、条件変数とロック保護を追加して、プロセス全体を定期的に実行できるようにし、ソフトウェアのロジックを保証します。
条件変数を追加せずにロック保護のみを追加すると、クリティカル セクションの 1 つのタイム スライスに 1 つのスレッド アクセスしか保証されず、クリティカル セクションへのアクセスの順序は保証されません。
C++ での条件変数の使用については、これらのリンクを参照してください。
https://en.cppreference.com/w/cpp/thread/condition_variable
https://stackoverflow.com/questions/73543664/do-i-need-to-lock-the-mutex-before-calling-condition-variablenotify
https://www.zhihu.com/question/541037047