Unix_Linux操作系统-笔记Day11(同步,竞争,互斥)

Day11

同步,竞争,互斥

#include 
#include 
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 
#include 
#include 

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 
#include 
#include 
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);
}
//两个线程都无法返回,产生死锁
  • 多个线程进行等待对方的资源,在得到所有资源继续运行前,都不会释放自己的资源,这样造成的循环等待现象,称为死锁

  • 构成死锁的四大必要条件:

    • 资源互斥
    • 占有,还想占有(请求并保持)
    • 资源不可剥夺
    • 环路(互相)等待
  • 防止死锁的方法:

    构成死锁的四个条件只要破坏其中一个就构不成死锁,死锁一旦形成,就无法消除,因此最好的方法就是避免产生死锁

    1. 破坏互斥条件,让资源能够共享,但缺点是不通过,因为有些资源不能共享,如打印机
    2. 破话请求并保持条件,采用预先分配的方法,在进程运行前一次性申请好它所需要的所有资源,缺点是浪费资源
    3. 破坏不可剥夺条件,对已占用资源的线程发送取消请求,但实现比较复杂,而且还会破破坏业务逻辑.
    4. 破破坏循环等待条件,为每一个资源进行编号,采用顺序的资源分配方法,规定每个线程必须按照递增的顺序请求资源,缺点是编号必须相对稳定,而且增加新的资源时会比较麻烦,而且有些特殊的业务逻辑不能完全按照指定的顺序分配资源

    避免死锁产生的算法(银行家算法)

    1. 申请资源的额度不能超过银行现有资源的总和
    2. 分批向银行贷款,但是贷款额度不能超过一开始最大需求总和
    3. 银行如果不能满足客户的需要,必须在有限时间内给出答复
    4. 客户必须在规定时间内还款

    如何检测死锁(判断一个进程的线程是否进入死锁)

    1. 画出资源分配图,并简化模拟资源分析的过程
    2. 监控线程或进程的栈内存使用情况
    3. 设计看门狗机制(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 
#include 
#include 
#include 
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 
#include 
#include 
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 
#include 
#include 
#include 
#include 

#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 
#include 
#include 
//定义并初始化条件变量
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

}


哲学家就餐

哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。

你可能感兴趣的:(笔记)