06.线程同步

互斥锁(互斥量)

描述

一个进程下的线程是共享资源的,通信便利的同时也造成了许多麻烦,线程程和线程之间如果同时访问一块资源就会出错,所以要引入一个互斥变量给它加锁,让它去协同不同线程程之间的访问(线程同步)

pthread_mutex_init函数

函数描述

初始化一个互斥量

函数原型

int pthread_mutex_init(pthread_mutex_t *mtx,const pthread_mutexattr *attr)
参一:传出参数,互斥量
参二:互斥量属性的指针

pthread_mutex_lock函数

阻塞加锁,如果没有加锁就加锁,有锁的话阻塞等待锁的消失(对于互斥量而言)
int pthread_mutex_lock(pthread_mutex_t *mtx)

pthread_mutex_unlock函数

int pthread_mutex_unlock(pthread_mutex_t *mtx)

pthread_mutex_destroy函数

int pthread_mutex_destroy(pthread_mutex_t *mtx)
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
const int N=1e6;
int a;
pthread_mutex_t mtx;//定义互斥变量
void* thread1(void* arg){
    for(int i = 1;i <= N; i++){
        //首先会获取mtx,判断mtx是否加锁,如果加锁了,会阻塞等待
        //知道持有互斥锁的线程解锁,然后当前进程加锁
        //如果没有加锁,直接给mtx上锁继续执行下面代码
        pthread_mutex_lock(&mtx);
        a++;
        //给互斥变量解锁
        pthread_mutex_unlock(&mtx);
        }
}
int main(int argc, char* argv[])
{
    pthread_t tids[10];
    pthread_mutex_init(&mtx,NULL);//初始化互斥变量

    for(int i = 0;i < 10; i++){
        pthread_create(tids+i,NULL,thread1,NULL);
    }
    for(int i = 0;i < 10; i++){
        pthread_join(tids[i],NULL);
    }
    printf("a = %d\n",a);
    pthread_mutex_destroy(&mtx);
    return 0;
}

pthread_mutex_trylock函数

非阻塞加锁(可以解决死锁问题)

练习

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<time.h>
int seat[11],seat_num=10,thread_num=12;
pthread_t thread[13];
pthread_mutex_t mtx;
int get_id(){
    //利用随机函数生成随机数,每次生成的随机数与末尾的调换
    int r_id,res;
    if(seat_num==0) return 0;
    r_id=rand()%seat_num+1;
    res=seat[r_id];
    seat[r_id]=seat[seat_num];
    seat_num--;
    return res;
}
void* thread1(void* arg){
    pthread_mutex_lock(&mtx);
    int seat_id=get_id();
    pthread_mutex_unlock(&mtx);
    if(seat_id==0){
        printf("用户 %d 选座失败,座位已售空\n",*(int*)arg);
    }else{
        printf("用户 %d 成功选得座位 %d\n",*(int*)arg,seat_id);
    }
    return NULL;
}
int main(int argc, char* argv[])
{
    srand((unsigned)time(0));
    for(int i=1;i<=10;i++){
        seat[i]=i;
    }
    pthread_mutex_init(&mtx,NULL);
    for(int i=1;i<=12;i++){
        int* user_id=(int*)malloc(sizeof(int));
        *user_id=i;
        pthread_create(&thread[i],NULL,thread1,user_id);
    }
    for(int i=1;i<=12;i++){
        pthread_join(thread[i],NULL);
    }
    pthread_mutex_destroy(&mtx);
    return 0;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

死锁现象

描述

当多个线程为了保护多个共享资源使用了多个互斥锁,如果使用不恰当,就可能造成多个线程之间一直阻塞等待对方锁的释放,这种现象叫死锁
具体例子

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在又俩个线程,A线程和B线程,现在同时运行,A线程给互斥量1上了锁,B线程给互斥量2上了锁,然后sleep了1s,此时A线程持有互斥量1并等待B线程互斥量2解锁,B线程中持有互斥量2并等待A线程互斥量1解锁,AB线程互相等待,产生死锁
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<time.h>
pthread_mutex_t mtx1,mtx2;
void* thread1(void* arg){
    printf("child get mtx1.........\n");
    pthread_mutex_lock(&mtx1);
    printf("child get mtx1 success!\n");
    sleep(1);
    printf("child get mtx2.........\n");
    pthread_mutex_lock(&mtx2);
    printf("child get mtx2 success!\n");
    pthread_mutex_unlock(&mtx1);
    pthread_mutex_unlock(&mtx2);
}
int main(int argc, char* argv[])
{
    pthread_mutex_init(&mtx1,NULL);
    pthread_mutex_init(&mtx2,NULL);
    pthread_t tid;
    pthread_create(&tid,NULL,thread1,NULL);
    printf("main get mtx2.........\n");
    pthread_mutex_lock(&mtx2);
    printf("main get mtx2 success!\n");
    sleep(1);
    printf("main get mtx1.........\n");
    pthread_mutex_lock(&mtx1);
    printf("main get mtx1 success!\n");
    pthread_mutex_unlock(&mtx2);
    pthread_mutex_unlock(&mtx1);
    return 0;
}

死锁产生的四个必要条件

1.互斥条件:必须得有多个互斥变量

2.持有并等待条件:A持有锁1并等待B锁2解锁,B持有锁2并等待A锁1解锁

3.不可剥夺条件:必须一直阻塞

4.环路等待条件

避免死锁

1.不加锁(不太现实奥)

2.锁的力度控制:尽量减少持有锁的事件,降低死锁发生概率

3.资源有序分配:破坏环路条件1 2 1 2

4.重试机制:破坏不可剥夺条件,一般为非阻塞的trylock

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<time.h>
#include <errno.h>
pthread_mutex_t mtx1,mtx2;
void* thread1(void* arg){
    while(1){
        printf("child get mtx1.........\n");
        pthread_mutex_lock(&mtx1);
        printf("child get mtx1 success!\n");
        sleep(1);
        printf("child get mtx2.........\n");
        int ret = pthread_mutex_trylock(&mtx2);
        if(ret != 0 && ret == EBUSY){
            pthread_mutex_unlock(&mtx1);
            printf("child free mtx1\n");
            continue;
        }
        printf("child get mtx2 success!\n");
        pthread_mutex_unlock(&mtx1);
        pthread_mutex_unlock(&mtx2);
        return NULL;
    }
}
int main(int argc, char* argv[])
{
    pthread_mutex_init(&mtx1,NULL);
    pthread_mutex_init(&mtx2,NULL);
    pthread_t tid;
    pthread_create(&tid,NULL,thread1,NULL);
    printf("main get mtx2.........\n");
    pthread_mutex_lock(&mtx2);
    printf("main get mtx2 success!\n");
    sleep(1);
    printf("main get mtx1.........\n");
    pthread_mutex_lock(&mtx1);
    printf("main get mtx1 success!\n");
    pthread_mutex_unlock(&mtx2);
    pthread_mutex_unlock(&mtx1);
    pthread_exit(NULL);
    return 0;
}

读写锁

已经有互斥锁了为什么还要有读写锁?

当多个线程读多写少的时候建议使用读写锁

概述

读写锁由读锁和写锁两部分构成,特性为写独占,读共享。
适用于写多读少的情况

特性

写独占,读共享

一个线程写的时候,另一个线程不能写
一个线程读的时候,另一个线程可以读

读写锁函数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

场景分析

持有读锁时,申请读锁,加锁成功,不需要等待
持有写锁时,申请写锁,会阻塞等待写锁的解锁,然后再加锁
持有读锁时,申请写锁,会阻塞等待读锁的解锁,然后再加锁
持有写锁时,申请读锁,会阻塞等带写锁的写锁,然后再加锁
持有读锁时,申请写锁和读锁,读锁会加锁成功,写锁会阻塞等待(如果是很多的读锁和少量的写锁的话,写锁会一直无法请求成功,造成饥饿)
持有写锁时,申请写锁和读锁,(如果是读优先)读锁会加锁成功,写锁阻塞等待写锁解锁,然后再加锁
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<time.h>
#include <errno.h>
pthread_rwlock_t rwlock;
void* thread1(void* arg){
    pthread_rwlock_wrlock(&rwlock);
    printf(" thread1 加锁成功\n");
    sleep(2);
    pthread_rwlock_unlock(&rwlock);
    printf("thread1 解锁\n");

}
void* thread2(void* arg){
    pthread_rwlock_wrlock(&rwlock);
    printf(" thread2 加锁成功\n");
    sleep(2);
    pthread_rwlock_unlock(&rwlock);
    printf("thread2 解锁\n");
}
void* thread3(void* arg){
    pthread_rwlock_rdlock(&rwlock);
    printf(" thread3 加锁成功\n");
    sleep(2);
    pthread_rwlock_unlock(&rwlock);
    printf("thread3 解锁\n");
}
int main(int argc, char* argv[])
{
    pthread_rwlock_init(&rwlock,NULL);
    pthread_t tid;
    pthread_create(&tid,NULL,thread1,NULL);
    sleep(1);
    pthread_create(&tid,NULL,thread2,NULL);
    sleep(1);
    pthread_create(&tid,NULL,thread3,NULL);
    pthread_exit(NULL);
    return 0;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

练习

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<time.h>
#include <errno.h>
pthread_rwlock_t rwlock;
int balance;

void* save(void* arg){
    int val = atoi((char*)arg);
    pthread_rwlock_wrlock(&rwlock);
    sleep(1);
    balance+=val;
    printf("save money:%d , balance:%d\n",val,balance);
    pthread_rwlock_unlock(&rwlock);

}
void* query(void* arg){
    pthread_rwlock_rdlock(&rwlock);
    int r = rand()%3+1;
    sleep(r);
    printf("query : balance : %d\n",balance);
    pthread_rwlock_unlock(&rwlock);
}

int main(int argc, char* argv[])
{
    srand((unsigned)time(0));
    pthread_t threads[15];
    pthread_rwlock_init(&rwlock,NULL);
    for(int i=0;i<5;i++){
        pthread_create(&threads[i],NULL,save,argv[i+1]);
    }
    for(int i=5;i<15;i++){
        pthread_create(&threads[i],NULL,query,NULL);
    }
    for(int i=0;i<15;i++){
        pthread_join(threads[i],NULL);
    }
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

条件变量

条件变量用于进程同步当中。条件变量允许一个线程阻塞等待某个条件的成立,改线程会被挂起,不占用cpu资源。当条件满足的时候,可以唤醒阻塞等待的线程继续执行
条件变量一般与互斥锁一同使用

函数

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
//条件变量的初始化
int pthread_cond_destroy(pthread_cond_t *cond)
//条件变量的销毁
int pthread_cond_wait(pthread_cond_t *t)
//阻塞等待某个条件变量,释放持有的互斥锁,这俩步是原子操作
//直到被唤醒,接触阻塞重新获得互斥锁
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
int a;
pthread_mutex_t mtx;
pthread_cond_t cond;
void* thread1(void* arg){
    for(int i=0;i<10;i++){
        //在哪访问共享资源就在哪加锁
        pthread_mutex_lock(&mtx);
        while (a != 0)
        {
            //避免轮询操作
            pthread_cond_wait(&cond,&mtx);
            //释放锁并阻塞等待,这两步是原子操作不可分割 
        }
        printf("a = %d\n", ++a);
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mtx);

    }  
}
void* thread2(void* arg){
    for(int i=0;i<10;i++){
        pthread_mutex_lock(&mtx);
        while (a != 1)
        {
            pthread_cond_wait(&cond,&mtx);
        }
        printf("a = %d\n", --a);
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mtx);
    }
}
int main(int argc, char* argv[])
{
    pthread_mutex_init(&mtx,NULL);
    pthread_t tid1,tid2,tid3,tid4;
    pthread_create(&tid1,NULL,thread1,NULL);
    pthread_create(&tid2,NULL,thread2,NULL);
    pthread_create(&tid3,NULL,thread1,NULL);
    pthread_create(&tid4,NULL,thread2,NULL);
    
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_join(tid3,NULL);
    pthread_join(tid4,NULL);
    return 0;
}

生产者消费者问题

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<time.h>
#include<stdlib.h>

pthread_mutex_t mtx;
pthread_cond_t cond;

struct node* head;

struct node{
    int val;//数据域
    struct node* next;//指针域
};

void add_val(int val){
    struct node* p=(struct node*)malloc(sizeof(struct node));
    p->val=val;
    p->next=head;
    head=p;
}

int get_val(){
    if(!head){
        return -1;
    }
    int ans=head->val;
    struct node* tmp=head;
    head = head->next;
    free(tmp);
    return ans;
}

void* producer(void* arg){
    while(1){
        pthread_mutex_lock(&mtx);
        add_val(rand()%500+1);
        //说明肯定有数据了
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mtx);
    }
}
void* conmuser(void* arg){
    while(1){
        pthread_mutex_lock(&mtx);
        //如果链表中没有数据了,那么就需要阻塞等待数据的产生
        int num=get_val();
        if(num == -1){
            pthread_cond_wait(&cond,&mtx);
        }
        printf("num = %d\n",num);
        sleep(1);
        pthread_mutex_unlock(&mtx);

    }
}
int main(int argc, char* argv[])
{
    srand(time(0));
    pthread_mutex_init(&mtx,NULL);
    pthread_cond_init(&cond,NULL);
    pthread_t tid1,tid2,tid3,tid4;
    pthread_create(&tid1,NULL,producer,NULL);
    pthread_create(&tid2,NULL,conmuser,NULL);
    
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    return 0;
}

信号量

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

两个线程堆a自增1万次

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<semaphore.h>

int a;
sem_t sem;
void* thread1(void* arg){
    for(int i=0;i<100000;i++){
        sem_wait(&sem);
        a++;
        sem_post(&sem);
    }
}
int main(int argc, char* argv[])
{
    sem_init(&sem,0,1);
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,thread1,NULL);    
    pthread_create(&tid2,NULL,thread1,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    printf("a = %d\n",a);    
    return 0;
}
  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值