1. スレッドの同期
同期とは、あらかじめ決められた順序で実行される調整されたペースです。例: あなたは終わりです、私は言います。「同じ」という言葉は文字通り、一緒に行動するという意味で分かりやすいですが、そうではなく、調整、援助、相互協力を意味する言葉です。
たとえば、プロセスとスレッドの同期は、プロセスまたはスレッド A と B の間の連携として理解できます。A がある程度実行されると、B の特定の結果に依存するため、A は停止し、B に実行するよう通知し、B は次のように実行されます。と言い、その結果を A に渡します。A は動作を続けます。
マルチスレッド プログラミングでは、一部の機密データは複数のスレッドから同時にアクセスすることができません。このとき、同期アクセス テクノロジを使用して、常に 1 つのスレッドだけがデータにアクセスするようにします。データの整合性。
#include <stdio.h>
#include <pthread.h>
int num = 0;
void* run(void* arg)
{
for(int i=0; i<1000000; i++)
{
num++;
}
}
int main(int argc,const char* argv[])
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,run,NULL);
pthread_create(&tid2,NULL,run,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
printf("%d\n",num);
}
2. ミューテックスロック
注意:如果man手册中查不到这系列函数,可以安装以下内容:
sudo apt-get install glibc-doc
sudo apt-get install manpages-posix-dev
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
功能:定义并初始化互斥锁
int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t*
mutexattr);
功能:初始化一互斥锁,会被初始化为非锁定状态
int pthread_mutex_lock (pthread_mutex_t* mutex);
功能:加锁,当互斥锁已经是锁定状态时,调用者会阻塞,直到互斥被解开,当前线程才会加锁成功并返回。
int pthread_mutex_unlock (pthread_mutex_t* mutex);
功能:解锁,解锁后等待加锁的线程才能加锁成功。
int pthread_mutex_destroy (pthread_mutex_t* mutex);
功能:销毁锁
int pthread_mutex_trylock (pthread_mutex_t *__mutex)
功能:加测试锁,如果不加锁刚立即返回
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict abs_timeout);
功能:倒计时加锁,如果超时还不加上则立即返回。
struct timespec{
time_t tv_sec; /* Seconds. */
long int tv_nsec; /* Nanoseconds.*/ 1秒= 1000000000 纳秒
};
#include <stdio.h>
#include <pthread.h>
/*
执行流程:
1、互斥锁被初始化为非锁定状态
2、线程1调用pthread_mutex_lock函数,立即返回,互斥量呈锁定状态;
3、线程2调用pthread_mutex_lock函数,阻塞等待;
4、线程1调用pthread_mutex_unlock函数,互斥量呈非锁定状态;
5、线程2被唤醒,从pthread_mutex_lock函数中返回,互斥量呈锁定状态
*/
pthread_mutex_t mutex;
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int num = 0;
void* run(void* arg)
{
for(int i=0; i<1000000; i++)
{
pthread_mutex_lock(&mutex);
num++;
pthread_mutex_unlock(&mutex);
}
}
int main(int argc,const char* argv[])
{
pthread_mutex_init(&mutex,NULL);
pthread_t pid1,pid2;
pthread_create(&pid1,NULL,run,NULL);
pthread_create(&pid2,NULL,run,NULL);
pthread_join(pid1,NULL);
pthread_join(pid2,NULL);
pthread_mutex_destroy(&mutex);
printf("%d\n",num);
}
3. 読み書きロック
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
功能:定义并初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
功能:初始化读写锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
功能:加读锁,如果不能加则阻塞等待
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
功能:加写锁,如果不能加则阻塞等待
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
功能:尝试加读锁,如果不能加则立即返回
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
功能:尝试加写锁,如果不能加则立即返回
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,
const struct timespec *restrict abstime);
功能:带倒计时加读锁,超时则立即返回
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,
const struct timespec *restrict abstime);
功能:带倒计时加写锁,超时则立即返回
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:销毁读写锁
使用读写锁的线程应根据后续的操作进行加锁,如果只对数据进行读取则只加读锁即可,只有对数据进行修改时才应该加写锁,与互斥锁的区别是,它能让只读的线程加上锁,使用原理与文件锁一样。
线程A 线程B
读锁 读锁 OK
读锁 写锁 NO
写锁 读锁 NO
写锁 写锁 NO
#include <pthread.h>
// 创建读写锁
pthread_rwlock_t rwlock;
int num = 0;
void* run(void* arg)
{
for(int i=0; i<1000000; i++)
{
// 加写锁
pthread_rwlock_wrlock(&rwlock);
num++;
// 解锁
pthread_rwlock_unlock(&rwlock);
}
}
int main(int argc,const char* argv[])
{
// 初始化读写锁
pthread_rwlock_init(&rwlock,NULL);
pthread_t pid1,pid2;
pthread_create(&pid1,NULL,run,NULL);
pthread_create(&pid2,NULL,run,NULL);
pthread_join(pid1,NULL);
pthread_join(pid2,NULL);
printf("%d\n",num);
}
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
// 创建读写锁
pthread_rwlock_t rwlock;
void* run1(void* arg)
{
// 加读锁
pthread_rwlock_rdlock(&rwlock);
sleep(1);
printf("%lu 线程1加读锁成功!\n",pthread_self());
// 解锁
pthread_rwlock_unlock(&rwlock);
}
void* run2(void* arg)
{
// 加读锁
pthread_rwlock_rdlock(&rwlock);
sleep(1);
printf("%lu 线程2加读锁成功!\n",pthread_self());
// 解锁
pthread_rwlock_unlock(&rwlock);
}
int main(int argc,const char* argv[])
{
// 初始化读写锁
pthread_rwlock_init(&rwlock,NULL);
pthread_t pid1,pid2;
pthread_create(&pid1,NULL,run1,NULL);
pthread_create(&pid2,NULL,run2,NULL);
pthread_join(pid1,NULL);
pthread_join(pid2,NULL);
// 销毁读写锁
pthread_rwlock_destroy(&rwlock);
}
4. デッドロック問題
デッドロックとは:
複数のスレッドが互いのリソースを待ち、必要なリソースを取得するまで自分のリソースを解放しないため、デッドロックと呼ばれる循環待機現象が発生します。
デッドロックが発生するには、次の 4 つの条件が必要です。
1. リソースの相互排他
2. 持って待つ
3. リソースは譲渡できません
4. ループ待ち
上記 4 つの条件は必須であり、どれか 1 つが満たされない限りデッドロックにはなりません。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
// 创建三个互斥锁并初始化
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
void* run1(void* arg)
{
pthread_mutex_lock(&mutex1);
usleep(100);
pthread_mutex_lock(&mutex2);
printf("没有构成死锁!!!\n");
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
}
void* run2(void* arg)
{
pthread_mutex_lock(&mutex2);
usleep(100);
pthread_mutex_lock(&mutex3);
printf("没有构成死锁!!!\n");
pthread_mutex_unlock(&mutex3);
pthread_mutex_unlock(&mutex2);
}
void* run3(void* arg)
{
pthread_mutex_lock(&mutex3);
usleep(100);
pthread_mutex_lock(&mutex1);
printf("没有构成死锁!!!\n");
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex3);
}
int main(int argc,const char* argv[])
{
// 创建三个线程
pthread_t tid1,tid2,tid3;
pthread_create(&tid1,NULL,run1,NULL);
pthread_create(&tid2,NULL,run2,NULL);
pthread_create(&tid3,NULL,run3,NULL);
// 主线程等待三个子线程结束
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
return 0;
}
デッドロックを防ぐ方法:
デッドロックを構成する 4 つの条件のうち 1 つだけが満たされない場合、デッドロックは発生しません。
1. 排他条件を解消し、リソースを共有できるようにします(複数のコピーを用意します)。
2. 所有と待機の条件を破棄し、必要なすべてのリソースを一度に申請し、リソースが満たされるまで実行させず、一度実行を開始すると、常にその所有物になります。システムリソースが無駄に消費されることになります。
3. 不可譲条件を破ります。すでに一部のリソースを占有していて、新しいリソースを要求しても取得できない場合、すでに取得しているリソースを解放します。欠点は、実装がより複雑で、すでに所有しているリソースを解放することです。取得した情報が以前の問題を引き起こす可能性があり、作業が 1 段階無駄になります。
4. 循環待機の条件を破壊し、リソースを順次割り当てる方法を採用し、システム内のリソースに番号を付け、スレッドが番号の大きい順にリソースを取得する必要があると規定します。欠点は、リソースが比較的安定している必要があるため、リソースが制限されることです。資源の増減。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
// 创建三个互斥锁并初始化
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
void* run1(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex1);
usleep(100);
if(0 == pthread_mutex_trylock(&mutex2))
break;
pthread_mutex_unlock(&mutex1);
}
printf("没有构成死锁!!!\n");
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
}
void* run2(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex2);
usleep(100);
if(0 == pthread_mutex_trylock(&mutex3))
break;
pthread_mutex_unlock(&mutex2);
}
printf("没有构成死锁!!!\n");
pthread_mutex_unlock(&mutex3);
pthread_mutex_unlock(&mutex2);
}
void* run3(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex3);
usleep(100);
if(0 == pthread_mutex_trylock(&mutex1))
break;
pthread_mutex_unlock(&mutex3);
}
printf("没有构成死锁!!!\n");
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex3);
}
int main(int argc,const char* argv[])
{
// 创建三个线程
pthread_t tid1,tid2,tid3;
pthread_create(&tid1,NULL,run1,NULL);
pthread_create(&tid2,NULL,run2,NULL);
pthread_create(&tid3,NULL,run3,NULL);
// 主线程等待三个子线程结束
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
return 0;
}
デッドロックを検出する方法:
一般的な考え方: 観察 + 分析
方法 1: コードを読み取り、各スレッドのロック ステップを分析します。
方法 2: strace を使用してプログラムの実行フローをトレースします。
方法 3: ログウォッチャーの業務実行プロセスを表示します。
方法 4: gdb を使用してデバッグし、各スレッドの実行ステータスを確認します。
1、把断点打在线程创建完毕后
2、run
3、info threads 查看所有线程
4、thread n 进程指定的线程
5、bt 查看线程堆栈信息
6、配合s/n单步调试
5. アトミック操作
いわゆるアトミック操作は、分割できない操作です。グローバル変数に対するマルチスレッド操作の場合、スレッド ロックは必要ありません。pthread_mutex_t と同じ保護効果があり、スレッドセーフでもあります。コンパイラは、「-march=i686 コンパイル パラメータを追加する必要がある」を使用するときにこれを使用します。
type __sync_fetch_and_add (type *ptr, type value); // +
type __sync_fetch_and_sub (type *ptr, type value); // -
type __sync_fetch_and_and (type *ptr, type value); // &
type __sync_fetch_and_or (type *ptr, type value); // |
type __sync_fetch_and_nand (type *ptr, type value); // ~
type __sync_fetch_and_xor (type *ptr, type value); // ^
功能:以上操作返回的是*ptr的旧值
type __sync_add_and_fetch (type *ptr, type value); // +
type __sync_sub_and_fetch (type *ptr, type value); // -
type __sync_and_and_fetch (type *ptr, type value); // &
type __sync_or_and_fetch (type *ptr, type value); // |
type __sync_nand_and_fetch (type *ptr, type value); // ~
type __sync_xor_and_fetch (type *ptr, type value); // ^
功能:以上操作返回的是*ptr与value计算后的值
type __sync_lock_test_and_set (type *ptr, type value);
功能:把value赋值给*ptr,并返回*ptr的旧值
__sync_lock_release(type *ptr);
功能:将*ptr赋值为0
#include <stdio.h>
#include <pthread.h>
int num = 0;
void* run(void* arg)
{
for(int i=0; i<100000000; i++)
{
__sync_fetch_and_add(&num,1);
}
}
int main(int argc,const char* argv[])
{
pthread_t pid1,pid2;
pthread_create(&pid1,NULL,run,NULL);
pthread_create(&pid2,NULL,run,NULL);
pthread_join(pid1,NULL);
pthread_join(pid2,NULL);
printf("%d\n",num);
}
アトミック操作の利点:
1.速度が非常に速い
2. デッドロックが発生しない
アトミック操作の欠点:
1. この関数は汎用ではないため、一部のコンパイラではサポートされていません。
2. type は整数関連の型のみにすることができ、浮動小数点型やカスタム型は使用できません。
6. 生産者と消費者のモデル
プロデューサ:データを生成するスレッド。このタイプのスレッドは、クライアントとクライアントからデータを受信し、そのデータをストレージ仲介者にプッシュする役割を果たします。
コンシューマ:データの消費と、プロデューサ スレッドによって生成されたデータの処理 (判断、フィルタリング、使用、応答、保存) を担当するスレッド。
ストレージ仲介:データ ウェアハウスとも呼ばれ、プロデューサー スレッドとコンシューマー スレッド間のデータ バッファーであり、両者の間の不均衡な生産速度と消費速度のバランスをとるために使用され、直接接続と比較して、バッファーを通じてプロデューサーとコンシューマーを分離します。両者の間で、お互いの待ち時間を回避し、業務効率を向上させます。
問題 1:生産は消費よりも速く、バッファーはいっぱいで、使い果たされます。
解決策: 生産を担当するスレッドは、消費を担当するスレッドに、フルスピードで消費してからスリープ状態になるように通知します。
問題 2:生産よりも消費が速く、バッファが空になり、飢餓が発生します。
解決策: 消費を担当するスレッドは、生産を担当するスレッドにフルスピードで生産するように通知し、その後スリープ状態になります。
7. 条件変数
条件変数は、スレッド間で共有される「グローバル変数」を使用した同期のメカニズムであり、主に次の 2 つのアクションが含まれます。
1. スレッドは「条件変数の条件が成立する」のを待ってスリープします。
2. 「条件が確立される」まで待機して、スリープ状態のスレッドを起動します。
競合状態を防ぐために、条件変数の使用は常にミューテックスと組み合わせて行われます。
int pthread_cond_init (pthread_cond_t* cond,const pthread_condattr_t* attr);
//亦可pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 使调用线程睡入条件变量cond,同时释放互斥锁mutex
int pthread_cond_wait (pthread_cond_t* cond,pthread_mutex_t* mutex);
int pthread_cond_timedwait (pthread_cond_t* cond,
pthread_mutex_t* mutex,
const struct timespec* abstime);
struct timespec {
time_t tv_sec; // Seconds
long tv_nsec; // Nanoseconds [0 - 999999999]
};
// 从条件变量cond中唤出一个线程,
// 令其重新获得原先的互斥锁
int pthread_cond_signal (pthread_cond_t* cond);
注意:被唤出的线程此刻将从pthread_cond_wait函数中返回,
但如果该线程无法获得原先的锁,则会继续阻塞在加锁上。
// 从条件变量cond中唤醒所有线程
int pthread_cond_broadcast (pthread_cond_t* cond);
// 销毁条件变量
int pthread_cond_destroy (pthread_cond_t* cond);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
// 仓库最大容量
#define STACK_MAX 50
// 仓库
char stack[STACK_MAX];
// 仓库的入口和出口
int top;
// 保护仓库入口的互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 当仓库满时线程睡入的条件变量
pthread_cond_t full = PTHREAD_COND_INITIALIZER;
// 当仓库空时线程睡入的条件变量
pthread_cond_t empty = PTHREAD_COND_INITIALIZER;
// 显示仓库内容
void show_stack(const char* opt,char data)
{
if(50 < top || 0 > top)
{
printf("爆仓了 %d\n",top);
exit(0);
}
for(int i=0; i<top; i++)
{
printf("%c",stack[i]);
}
printf("%s%c\n",opt,data);
}
// 生产者
void* production(void* arg)
{
for(;;)
{
// 往仓库中push数据,加锁保护入口
pthread_mutex_lock(&mutex);
// 发现仓库满了
while(top >= STACK_MAX)
{
// 叫睡所有消费线程全速消费,虽然暂时只能醒来一个消费者线程,但所有消费线程已经加
入到争夺互斥锁的过程
pthread_cond_broadcast(&empty);
// 生产者线程睡入满仓条件变量
pthread_cond_wait(&full,&mutex);
}
// 生产数据
char data = rand()%26+'A';
// 显示仓库
show_stack("<-",data);
// 数据添加到仓库
stack[top++] = data;
// 模拟生产数据消耗的时间
usleep(rand()%5*10000);
// 此时仓库已经不空了,叫醒一个空仓条件休眠的消费者线程
pthread_cond_signal(&empty);
// 生产完毕解锁入口
pthread_mutex_unlock(&mutex);
}
}
// 消费者
void* consumption(void* arg)
{
for(;;)
{
// 从仓库中消费数据,加锁保护出口
pthread_mutex_lock(&mutex);
// 发现空仓
while(0 == top)
{
// 叫醒所有生产线程全速生产,虽然暂时只能醒来一个生产者线程,但所有生产者线程都加
入到争夺互斥锁的过程
pthread_cond_broadcast(&full);
// 消费者线程睡入空仓条件变量
pthread_cond_wait(&empty,&mutex);
}
// 从仓库中消费数据
char data = stack[top--];
// 显示仓库内容
show_stack("->",data);
// 模拟消费数据所消耗的时间
usleep(rand()%5*10000);
// 此时已经不满,叫醒一个因为满仓而休眠的线程
pthread_cond_signal(&full);
// 消费完毕解锁出口
pthread_mutex_unlock(&mutex);
}
}
int main(int argc,const char* argv[])
{
srand(time(NULL));
pthread_t tids[10];
for(int i=0; i<10; i++)
{
if(i%2)
pthread_create(tids+i,NULL,production,NULL);
else
pthread_create(tids+i,NULL,consumption,NULL);
}
for(int i=0; i<10; i++)
{
pthread_join(tids[i],NULL);
}
return 0;
}
8、セマフォ
複数のスレッドで使用されるセマフォ:
#include <semaphore.h>
sem_t sem;
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:给信号量设置初始值
pshared:信号量的使用范围
0 线程间使用
nonzero 进程之间使用
int sem_wait(sem_t *sem);
功能:信号量减1操作,如果信号量已经等于0,则阻塞
int sem_trywait(sem_t *sem);
功能:尝试对信号量减1操作,能减返回0成功,不能减返回-1失败,不会阻塞
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
功能:带倒计时的对信号减1操作,能减返回0成功,不能减超时返回-1失败,阻塞abs_timeout一段时间
int sem_post(sem_t *sem);
功能:对信号量执行加1操作
int sem_getvalue(sem_t *sem, int *sval);
功能:获取信号量的值
int sem_destroy(sem_t *sem);
功能:销毁信号量
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
// 仓库最大容量
#define STACK_MAX 50
// 仓库
char stack[STACK_MAX];
// 仓库的入口和出口
int top;
// 定义信号量
sem_t data_sem,null_sem;
// 显示仓库内容
void show_stack(const char* opt,char data)
{
if(50 < top || 0 > top)
{
printf("爆仓了 %d\n",top);
exit(0);
}
for(int i=0; i<top; i++)
{
printf("%c",stack[i]);
}
printf("%s%c\n",opt,data);
}
// 生产者
void* production(void* arg)
{
for(;;)
{
// 空位置的数量减1
sem_wait(&null_sem);
// 生产数据
char data = rand()%26+'A';
// 显示仓库
show_stack("<-",data);
// 数据添加到仓库
stack[top] = data;
__sync_fetch_and_add(&top,1);
usleep(rand()%100*100);
// 数量的数量加1
sem_post(&data_sem);
}
}
// 消费者
void* consumption(void* arg)
{
for(;;)
{
// 数据的数量减1
sem_wait(&data_sem);
// 从仓库中消费数据
char data = stack[top];
__sync_fetch_and_sub(&top,1);
// 显示仓库内容
show_stack("->",data);
// 模拟消费数据所消耗的时间
usleep(rand()%1000*10);
// 空位置的数量加1
sem_post(&null_sem);
}
}
int main(int argc,const char* argv[])
{
srand(time(NULL));
// 初始化空位置的数量
sem_init(&null_sem,0,STACK_MAX);
// 初始化数据的数量
sem_init(&data_sem,0,0);
pthread_t tids[10];
for(int i=0; i<10; i++)
{
if(i<5)
pthread_create(tids+i,NULL,production,NULL);
else
pthread_create(tids+i,NULL,consumption,NULL);
}
for(int i=0; i<10; i++)
{
pthread_join(tids[i],NULL);
}
return 0;
}
複数のプロセスで使用されるセマフォ:
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
功能:在内核创建一个信号量对象
name:信号量的名字
oflag:
O_CREAT 不存在则创建信号量,存在则获取
O_EXCL 如果信号量已经存在,返回失败
mode:信号量的权限
value:信号量的初始值
sem_t *sem_open(const char *name, int oflag);
功能:获取信号,或相关属性
int sem_unlink(const char *name);
功能:删除信号量
// 进程A
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
int main(int argc,const char* argv[])
{
sem_t* sem = sem_open("sem_name",O_CREAT,0644,0);
for(;;)
{
sem_post(sem);
sleep(1);
int value = 0;
sem_getvalue(sem,&value);
printf("%d\n",value);
}
return 0;
}
// 进程B
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
int main(int argc,const char* argv[])
{
sem_t* sem = sem_open("sem_name",O_CREAT);
for(;;)
{
sem_wait(sem);
sleep(2);
int value = 0;
sem_getvalue(sem,&value);
printf("%d\n",value);
}
return 0;
}