Day11
同步,竞争,互斥
#include <stdio.h>
#include <pthread.h>
int num = 0;
void* start_run(void* arg){
for(int i=0;i<1000000;i++){
num++;
}
}
int main(){
pthread_t pid[10] = {};
for(inti =0;i<10;i++){
pthread_create(&pid[i],NULL,start_run,NULL);
}
for(int i=0;i<10;i++){
pthread_join(pid[i],NULL);
}
printf("%d\n",num);
}
//运行结果600W+,理论是1000W,为啥?
- 当多个线程同时访问其共享的资源时,需要相互协调,以防止出现数据不一致,不完整的问题,能达到这种状态叫线程同步
- 有些资源在同一时刻只有一个线程访问,对于这种资源的访问需要竞争
- 当资源获取到后,能够防止资源被其他线程再次获取的方法叫互斥
互斥量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(pthread_mutex_t * __mutex,__const pthread_mutexattr_t *__mutexattr);
- 初始化互斥量,使用第二个互斥量来初始化第一个互斥量,如果第二个为空,则使用默认参数初始化互斥量,也可以使用宏来初始化
int pthread_mutex_destroy(pthread_mutex_t *__mutex);
- 销毁互斥量
- 注意 互斥量是一个结构体,里面有成员是指针,指向了堆内存数据,需要显式初始化函数及销毁函数
- 如果使用堆内存存储互斥量,销毁后还要free
int pthread_mutex_trylock(pthread_mutex_t *__mutex);
- 尝试锁定互斥量
int pthread_mutex_lock(pthread_mutex_t *__mutex);
- 锁定互斥量,当互斥量已经是锁定状态,此函数阻塞(直到互斥量在其他线程中解锁,调用者线程加锁成功才返回)
- 互斥量一旦加锁,只有它自己能解
int pthread_mutex_timedlock(pthread_mutex_t *__restrict __mutex,const struct timespec *__restrict_abstime);
- 在指定时间内锁定一个互斥量,(使用的是系统时间)
struct timespec{ __time_t tv_sec; long int tv_nsec; }
int pthread_mutex_unlock (pthread_mutex_t *__mutex);
- 解锁
//用法1
#include <stdio.h>
#include <pthread.h>
#include <stdbool.h>
int num = 0;
//定义一个互斥量并初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* start_run(void* arg){
for(int i=0;i<1000000;i++){
pthread_mutex_lock(&mutex);
num++;
pthread_mutex_unlock(&mutex);
}
}
int main(){
pthread_t pid[10] = {};
for(inti =0;i<10;i++){
pthread_create(&pid[i],NULL,start_run,NULL);
}
for(int i=0;i<10;i++){
pthread_join(pid[i],NULL);
}
printf("%d\n",num);
pthread_mutex_destroy(&mutex);
}
//用法2
void* start_run(void* arg){
for(int i=0;i<1000000;i++){
pthread_mutex_lock((pthread_mutex_t*)arg);
num++;
pthread_mutex_unlock((pthread_mutex_t*)arg);
}
}
int main(){
pthread_mutex_t* mutex = malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(mutex);
pthread_t pid[10] = {};
for(inti =0;i<10;i++){
pthread_create(&pid[i],NULL,start_run,mutex);
}
for(int i=0;i<10;i++){
pthread_join(pid[i],NULL);
}
printf("%d\n",num);
pthread_mutex_destroy(&mutex);
}
死锁
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t key = PYHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mouse = PYHREAD_MUTEX_INITIALIZER;
void* ming(void* arg){
pthread_mutex_lock(&key);
usleep(100);
pthread_mutex_lock(&mouse);
printf("键盘和鼠标都拿到了,可以开心打游戏了\n");
sleep(5);
pthread_mutex_unlock(&mouse);
pthread_mutex_unlock(&key);
}
void* huang(void* arg){
pthread_mutex_lock(&mouse);
usleep(100);
pthread_mutex_lock(&key);
printf("键盘和鼠标都拿到了,可以开心ppt了\n");
sleep(5);
pthread_mutex_unlock(&mouse);
pthread_mutex_unlock(&key);
}
int main(){
pthread_t pid1;
pthread_create(pid1,NULL,ming,NULL);
pthread_T pid2;
pthread_create(pud2,NULL,huang,NULL);
pthread_join(pid1,NULL);
pthread_join(pid2,NULL);
}
//两个线程都无法返回,产生死锁
-
多个线程进行等待对方的资源,在得到所有资源继续运行前,都不会释放自己的资源,这样造成的循环等待现象,称为死锁
-
构成死锁的四大必要条件:
- 资源互斥
- 占有,还想占有(请求并保持)
- 资源不可剥夺
- 环路(互相)等待
-
防止死锁的方法:
构成死锁的四个条件只要破坏其中一个就构不成死锁,死锁一旦形成,就无法消除,因此最好的方法就是避免产生死锁
- 破坏互斥条件,让资源能够共享,但缺点是不通过,因为有些资源不能共享,如打印机
- 破话请求并保持条件,采用预先分配的方法,在进程运行前一次性申请好它所需要的所有资源,缺点是浪费资源
- 破坏不可剥夺条件,对已占用资源的线程发送取消请求,但实现比较复杂,而且还会破破坏业务逻辑.
- 破破坏循环等待条件,为每一个资源进行编号,采用顺序的资源分配方法,规定每个线程必须按照递增的顺序请求资源,缺点是编号必须相对稳定,而且增加新的资源时会比较麻烦,而且有些特殊的业务逻辑不能完全按照指定的顺序分配资源
避免死锁产生的算法(银行家算法)
- 申请资源的额度不能超过银行现有资源的总和
- 分批向银行贷款,但是贷款额度不能超过一开始最大需求总和
- 银行如果不能满足客户的需要,必须在有限时间内给出答复
- 客户必须在规定时间内还款
如何检测死锁(判断一个进程的线程是否进入死锁)
- 画出资源分配图,并简化模拟资源分析的过程
- 监控线程或进程的栈内存使用情况
- 设计看门狗机制(TCP心跳包)
信号量
线程的信号量,进程的信号量的机制是一样的,但使用方法不同,用于控制,管理线程间的共享资源
-
sem_init - initialize an unnamed semaphore
- 初始化信号量(创建信号量)
- sem 信号量ID 输出
- pshared 一般为0(线程间)进程中使用的
- 非零表示进程间使用,Linux不支持
- value 信号的初始化
-
int sem_wait(sem_t *sem);
- 信号量-1,不够减则阻塞(为0时)
-
int sem_trywait(sem_t *sem);
- 信号量-1,不够减则立即返回-1
-
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
- 信号量-1,不够减则阻塞,直到abs_timeout超时返回-1
-
int sem_post(sem_t *sem);
- 信号量+1
-
int sem_destroy(sem_t *sem);
- 销毁信号量
-
int sem_getvalue(sem_t *sem, int *sval);
- 获取信号量的值
#include <stdio.h>
#include <pthread.h>
#include <stdbool.h>
#include <semaphore.h>
int num;
sem_t sem;
void* start_run(void* arg){
for(int i=0;i<1000000;i++){
sem_wait(&sem);
num++;
sem_post(&sem);
}
}
int main(){
sem_init(&sem,0,1);
pthread_t pid[10] = {};
for(inti =0;i<10;i++){
pthread_create(&pid[i],NULL,start_run,NULL);
}
for(int i=0;i<10;i++){
pthread_join(pid[i],NULL);
}
printf("%d\n",num);
sem_destroy(&sem);
}
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem;
void* start_run(void* arg){
sem_wait(&sem);
int value = 0;
sem_getvalue(&sem,&value);
printf("%u:我骑走一头小毛驴,还有%d头小毛驴\n",pthread_self(),value);
sem_post(&sem);
sleep(rand()%10);
sem_getvalue(&sem,&value);
printf("%u:我骑走一头小毛驴,还有%d头小毛驴\n",pthread_self(),value);
}
int main(){
//初始化3头小毛驴
sem_init(&sem,0,3);
pthread_t pid[5];
for(int i=0;i<5;i++){
pthread_create(&pid[i],NULL,start_run,NULL);
}
for(int i=0;i<5;i++){
pthread_join(pid[i],NULL);
}
}
生产者与消费者
生产者与消费者模型
生产者:产生数据的线程 消费者:使用数据的线程
┌───────┐ ┌───────┐
|pthread│ ┌──────────────────────────┐ |pthread│
├───────┤ => |temporary buffer for datas| => ├───────┤
|pthread| └──────────────────────────┘ |pthread|
└───────┘ └───────┘
- 通过缓冲区来隔离生产者与消费者(提高安全性),与二者直连相比,避免互相等待,提高运行效率
- 问题1 消费过快生产跟不上,消费线程会饿死
- 问题2 生成过快消费太慢,仓库会爆仓
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#define HOUSE_MAX 20
//栈结构
char house[HOUSE_MAX] = {};
//栈顶下标
int top = 0;
//互斥量(确保只有一个线程访问栈顶)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//满仓条件变量(爆仓时,生产线程睡)
pthread_cond_t full = PTHREAD_COND_INITIALIZER;
//空仓条件变量(空仓时,消费线程睡)
pthread_cond_t null = PTHREAD_COND_INITIALIZER;
//显式仓库
void show_house(char* who, char* op, char ch){
printf("%s:",who);
for(int i=0; i<=top; i++){
printf("%c",house[i]);
}
printf("%s%c\n",op,ch);
}
//生产者线程
void* production(void* arg){
char* who = (char*)arg;
for(;;){
char ch = 'A'+rand()%26;
pthread_mutex_lock(&mutex);
//醒来后再次检查是否满仓
while(HOUSE_MAX <= top){
printf("%s:满仓\n",who);
pthread_cond_wait(&full,mutex);
}
//入仓数据
house[top++] = ch;
//模拟现时情况
usleep(rand()%100000);
//解锁
pthread_mutex_unlock(&mutex);
//已经确保仓库不空,通知消费
pthread_cond_signal(&null);
}
return NULL;
}
//消费者线程
void* consume(void* arg){
char* who = (char*)arg;
for(;;){
//加锁
pthread_mutex_lock(&mutex);
//检查仓库是否空
while(0 == top){
printf("%s:空仓\n",who);
pthread_cond_wait(&null,&mutex);
}
//消费数据
char ch = house[top--];
show_house(who,"->",ch);
usleep(rand()%100000);
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&full);
}
}
int main(){
srand(time(NULL));
pthread_t pid[6] = {};
pthread_create(&pid[0],NULL,production,"生产1");
pthread_create(&pid[1],NULL,production,"生产2");
pthread_create(&pid[2],NULL,production,"生产3");
pthread_create(&pid[3],NULL,consume,"消费1");
pthread_create(&pid[4],NULL,consume,"消费2");
pthread_create(&pid[5],NULL,consume,"消费3");
for(int i=0;i<6;i++){
pthread_join(pid[i]);
}
}
条件变量
-
int pthread_cond_init (pthread_cond_t *cond,pthread_condattr_t *__restrict_cond_attr);
- 初始化条件变量
- cond 待初始化的条件变量
- cond_attr 条件变量的属性
-
pthread_cond_destroy(pthread_cond_t *cond);
- 销毁条件变量
-
pthread_cond_singal(pthread_cond_t *cond);
- 唤醒条件变量中的一个线程(最早睡的)
- 注意 线程醒的前提是互斥量必须是解锁状态的,线程醒前会再次加锁,如果不能加锁就不会醒来
-
pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
- 让调用者线程进入睡眠,并解锁一个互斥量
- cond 线程谁入的条件变量
- mutex 线程睡眠前要解锁的互斥量(是不是锁定状态没有关系)
-
pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespace __abstimr)
- 让调用者线程进入睡眠(指定睡眠时间),并解锁一个互斥量
- 注意,使用的是系统时间
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//定义并初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* start_run(void* arg){
printf("我将进入睡眠\n");
pthread_cond_wait(&cond,&mutex);
printf("我醒了\n");
}
int main(){
pthread_t pid;
pthread_create(&pid,NULL,start_run,NULL);
sleep(3);
//pthread_mutex_lock(&mutex); 如果唤醒前加锁,无法唤醒
pthread_cond_signal(&cond);
pthread_join(pid,NULL);
}
void *start_run(void* arg){
struct timrval now;
struct timespec t;
gettimeofday(&now,NUll);
t.tv_sec =
pthread_cond_timewait
}
哲学家就餐
哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。