ホームページ  >  記事  >  システムチュートリアル  >  Linux マルチスレッド プログラミング ロックの詳細な説明: 競合とデッドロックを回避する方法

Linux マルチスレッド プログラミング ロックの詳細な説明: 競合とデッドロックを回避する方法

WBOY
WBOY転載
2024-02-11 16:30:22634ブラウズ

Linux マルチスレッド プログラミングでは、ロックはスレッド間の競合やデッドロックを回避できる非常に重要なメカニズムです。ただし、ロックが誤って使用されると、パフォーマンスの低下や異常な動作が発生する可能性があります。この記事では、Linux の一般的なロックの種類、それらの正しい使用方法、競合やデッドロックなどの問題を回避する方法を紹介します。

プログラミングでは、共有データ操作の整合性を確保するためにオブジェクト ミューテックス ロックの概念が導入されます。各オブジェクトは、「ミューテックス ロック」と呼ばれるマークに対応します。これは、常に 1 つのスレッドだけがオブジェクトにアクセスできるようにするために使用されます。 Linux で実装されているミューテックス ロックの仕組みには、POSIX ミューテックス ロックとカーネル ミューテックス ロックがありますが、この記事では主に POSIX ミューテックス ロック、つまりスレッド間ミューテックス ロックについて説明します。

セマフォはマルチスレッドとマルチタスクの同期に使用されます。1 つのスレッドが特定のアクションを完了すると、セマフォを通じて他のスレッドに通知し、他のスレッドが特定のアクションを実行します (全員が sem_wait 状態になると、そこでブロックされます)。 。ミューテックス ロックは、マルチスレッドおよびマルチタスクの相互排他に使用されます。1 つのスレッドが特定のリソースを占有している場合、他のスレッドはそのリソースにアクセスできません。このスレッドのロックが解除されるまで、他のスレッドはこのリソースの使用を開始できます。たとえば、グローバル変数にアクセスするには、操作の完了後にロックとロック解除が必要になる場合があります。場合によっては、ロックとセマフォが同時に使用されることもあります"

つまり、セマフォは必ずしも特定のリソースのロックに関するものではなく、プロセスの概念です。たとえば、2 つのスレッド A と B があります。B スレッドは、A スレッドが特定のタスクを完了するまで待機する必要があります。次のステップに進む前に、このタスクには必ずしも特定のリソースのロックが含まれるわけではありませんが、一部の計算やデータ処理を実行することもできます。スレッドミューテックスとは「あるリソースをロックする」という概念で、ロック期間中は他のスレッドが保護されたデータを操作できなくなります。場合によっては、この 2 つは交換可能です。

両者の違い:

######範囲######

セマフォ: プロセス間またはスレッド間 (Linux はスレッド間のみ) ミューテックスロック: スレッド間

ロック時

セマフォ: セマフォの値が 0 より大きい限り、他のスレッドは sem_wait を正常に実行できます。成功すると、セマフォの値は 1 減ります。値が 0 以下の場合、sem_post が解放された後に値が 1 増加するまで、sem_wait はブロックします。一言で言えば、セマフォの値>=0です。 ミューテックス ロック: ロックされている限り、他のスレッドは保護されたリソースにアクセスできません。ロックがない場合、リソースは正常に取得されますが、そうでない場合はブロックされ、リソースが使用可能になるまで待機します。一言で言えば、スレッド ミューテックスの値は負の値になる可能性があります。

マルチスレッド

スレッドはコンピューター内で独立して実行される最小単位であり、実行時に占有するシステム リソースはほとんどありません。マルチプロセスと比較すると、マルチプロセスにはマルチプロセスにはない利点がいくつかありますが、最も重要なことは、マルチスレッドの場合、マルチプロセスよりもリソースを節約できることです。

スレッドの作成

Linux では、新しく作成されたスレッドは元のプロセスにはありませんが、システムはシステム コールを通じて clone() を呼び出します。システムは、元のプロセスとまったく同じプロセスをコピーし、そのプロセス内でスレッド関数を実行します。 Linux では、スレッドは pthread_create() 関数を通じて作成されます。

pthread_create()

リーリー ###で:###

スレッドは pthread_t 型のポインタを表します; attr はスレッドのいくつかの属性を指定するために使用されます;

start_routine は、関数を呼び出すスレッドである関数ポインタを表します;

arg は、関数を呼び出すスレッドに渡されるパラメータを表します。

スレッドの作成に成功すると、関数 pthread_create() は 0 を返します。戻り値が 0 でない場合は、スレッドの作成に失敗したことを意味します。スレッド属性の場合、構造体 pthread_attr_t で定義されます。

スレッドの作成プロセスは次のとおりです:

リーリー

上記のコードでは、pthread_self() 関数が使用されていますが、この関数の機能は、このスレッドのスレッド ID を取得することです。 main 関数の sleep() は、メイン プロセスを待機状態にして、スレッドの実行が完了できるようにするために使用されます。最終的な実行効果は次のとおりです:

では、arg を使用してパラメータをサブスレッドに渡すにはどうすればよいでしょうか? 具体的な実装は次のとおりです:

リーリー

最終的な実行結果を次の図に示します。

Linux マルチスレッド プログラミング ロックの詳細な説明: 競合とデッドロックを回避する方法メインプロセスが早期に終了した場合はどうなりますか? 次のコードに示すように:

リーリー

この時点で、メイン プロセスが早期に終了し、プロセスはリソースをリサイクルします。この時点で、スレッドは実行を終了します。実行結果は次のとおりです:

Linux マルチスレッド プログラミング ロックの詳細な説明: 競合とデッドロックを回避する方法

スレッドがハングします

上記の実装プロセスでは、メインスレッドが終了する前に各サブスレッドの実行が完了するのを待機できるようにするために、free() 関数が使用されています。Linux マルチスレッドでは、pthread_join() 関数も使用できます。他のスレッドの場合、関数の具体的な形式は次のとおりです: Linux マルチスレッド プログラミング ロックの詳細な説明: 競合とデッドロックを回避する方法 リーリー

関数 pthread_join() はスレッドの終了を待機するために使用され、その呼び出しは一時停止されます。

一个线程仅允许一个线程使用pthread_join()等待它的终止。

如需要在主线程中等待每一个子线程的结束,如下述代码所示:

#include  
#include  
#include  
#include  
 
void* thread(void *id){ 
  pthread_t newthid; 
 
  newthid = pthread_self(); 
  int num = *(int *)id; 
  printf("this is a new thread, thread ID is %u,id:%d\n", newthid, num); 
 
  printf("thread %u is done\n", newthid); 
  return NULL; 
} 
int main(){ 
   int num_thread = 5; 
   pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); 
   int * id = (int *)malloc(sizeof(int) * num_thread); 
 
   printf("main thread, ID is %u\n", pthread_self()); 
   for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ 
         printf("thread create failed!\n"); 
         return 1; 
       } 
   } 
   for (int i = 0; i return 0; 
} 

最终的执行效果如下所示:

Linux マルチスレッド プログラミング ロックの詳細な説明: 競合とデッドロックを回避する方法

注:在编译的时候需要链接libpthread.a:

g++ xx.c -lpthread -o xx

互斥锁mutex

多线程的问题引入

多线程的最大的特点是资源的共享,但是,当多个线程同时去操作(同时去改变)一个临界资源时,会破坏临界资源。如利用多线程同时写一个文件:

#include  
#include  
 
const char filename[] = "hello"; 
 
void* thread(void *id){ 
  int num = *(int *)id; 
 
  // 写文件的操作 
  FILE *fp = fopen(filename, "a+"); 
  int start = *((int *)id); 
  int end = start + 1; 
  setbuf(fp, NULL);// 设置缓冲区的大小 
  fprintf(stdout, "%d\n", start); 
  for (int i = (start * 10); i "%d\t", i); 
  } 
  fprintf(fp, "\n"); 
  fclose(fp); 
  return NULL; 
} 
 
int main(){ 
   int num_thread = 5; 
   pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); 
   int * id = (int *)malloc(sizeof(int) * num_thread); 
 
   for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ 
         printf("thread create failed!\n"); 
         return 1; 
         } 
   } 
   for (int i = 0; i return 0; 
} 

执行以上的代码,我们会发现,得到的结果是混乱的,出现上述的最主要的原因是,我们在编写多线程代码的过程中,每一个线程都尝试去写同一个文件,这样便出现了上述的问题,这便是共享资源的同步问题,在Linux编程中,线程同步的处理方法包括:信号量,互斥锁和条件变量。

互斥锁

互斥锁是通过锁的机制来实现线程间的同步问题。互斥锁的基本流程为:

初始化一个互斥锁:pthread_mutex_init()函数

加锁:pthread_mutex_lock()函数或者pthread_mutex_trylock()函数

对共享资源的操作

解锁:pthread_mutex_unlock()函数

注销互斥锁:pthread_mutex_destory()函数

其中,在加锁过程中,pthread_mutex_lock()函数和pthread_mutex_trylock()函数的过程略有不同:

当使用pthread_mutex_lock()函数进行加锁时,若此时已经被锁,则尝试加锁的线程会被阻塞,直到互斥锁被其他线程释放,当pthread_mutex_lock()函数有返回值时,说明加锁成功;

而使用pthread_mutex_trylock()函数进行加锁时,若此时已经被锁,则会返回EBUSY的错误码。

同时,解锁的过程中,也需要满足两个条件:

解锁前,互斥锁必须处于锁定状态;

必须由加锁的线程进行解锁。

当互斥锁使用完成后,必须进行清除。

有了以上的准备,我们重新实现上述的多线程写操作,其实现代码如下所示:

#include  
#include  
 
pthread_mutex_t mutex; 
 
const char filename[] = "hello"; 
 
void* thread(void *id){ 
 
   int num = *(int *)id; 
   // 加锁 
 
   if (pthread_mutex_lock(&mutex) != 0){ 
     fprintf(stdout, "lock error!\n"); 
   } 
   // 写文件的操作 
   FILE *fp = fopen(filename, "a+"); 
   int start = *((int *)id); 
   int end = start + 1; 
   setbuf(fp, NULL);// 设置缓冲区的大小 
fprintf(stdout, "%d\n", start); 
   for (int i = (start * 10); i "%d\t", i); 
   } 
   fprintf(fp, "\n"); 
   fclose(fp); 
 
   // 解锁 
   pthread_mutex_unlock(&mutex); 
   return NULL; 
} 
 
int main(){ 
   int num_thread = 5; 
   pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); 
   int * id = (int *)malloc(sizeof(int) * num_thread); 
 // 初始化互斥锁 
   if (pthread_mutex_init(&mutex, NULL) != 0){ 
     // 互斥锁初始化失败 
     free(pt); 
     free(id); 
     return 1; 
   } 
   for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ 
         printf("thread create failed!\n"); 
         return 1; 
      } 
   } 
   for (int i = 0; i return 0; 
} 

最终的结果为:

Linux マルチスレッド プログラミング ロックの詳細な説明: 競合とデッドロックを回避する方法

通过本文的介绍,您应该已经了解了Linux多线程编程中的常见锁类型、正确使用锁的方法以及如何避免竞争和死锁等问题。锁机制是多线程编程中必不可少的一部分,掌握它们可以使您的代码更加健壮和可靠。在实际应用中,应该根据具体情况选择合适的锁类型,并遵循最佳实践,以确保程序的高性能和可靠性。

以上がLinux マルチスレッド プログラミング ロックの詳細な説明: 競合とデッドロックを回避する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlxlinux.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。