• Linux多线程C++版(五) 线程同步和线程互斥


    1.线程同步和线程互斥
    • 线程同步
      • 线程同步是一个宏观概念,在微观上包含线程的相互排斥和线程先后执行的约束问题。
      • 解决同步方式
        • 条件变量
        • 线程信号量
    • 线程互斥
      • 线程执行是相互排斥
      • 解决互斥方式
        • 互斥锁
        • 读写锁
        • 线程信号量
    2.线程互斥案例—ATM取钱–没有使用互斥锁

    头文件 account.h

    #ifndef __ACCOUNT_H__
    #define __ACCOUNT_H__
    //银行账户结构体
    typedef struct{
        int code;//卡号
        double balance;//余额
    }Account;
    //创建账户
    extern Account* creat_account(int code,double balance);
    //销毁账户
    extern void destory_account(Account *a);
    //取款
    extern double withdraw(Account *a,double amt);
    //存款
    extern double deposit(Account *a,double amt);
    //查看账户余额
    extern double get_balance(Account *a);
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    功能文件account.c

    #include "account.h"
    #include 
    #include 
    #include 
    
    //创建账户
    Account* creat_account(int code,double balance){
        //创建账户在堆当中
        Account *a = (Account*)malloc(sizeof(Account));
        assert(a != NULL);//用来判断a指针所需要开辟的空间,开辟成功了吗,成功就继续,否则就程序终止运行。
        a->code = code;
        a->balance = balance;
        return a;
    }
    //销毁账户
    void destory_account(Account *a){
        assert(a != NULL);
        free(a);//释放空间
    }
    //取款
    double withdraw(Account *a,double amt){
        if(amt < 0 || amt > a->balance){
            return 0.0;
        }
        double balance = a->balance;
        sleep(1);
        balance -= amt;
        a->balance = balance;
        return amt;
    }
    //存款
    double deposit(Account *a,double amt){
       assert(a != NULL);
       if(amt < 0){
           return 0.0;
       }
        double balance = a->balance;
        sleep(1);
        balance += amt;
        a->balance = balance;
        return amt;
    }
    //查看账户余额
    double get_balance(Account *a){
        assert(a != NULL);
        double balance = a->balance;
        return balanec;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    执行文件 account_test.c

    #include "account.h"
    #include 
    #include 
    #include 
    #include 
    
    //操作者的结构体
    typedef struct{
        char  name[20];//操作者姓名
        Account *account;//账户
        double amt;//金额
    }OperArg
    //定义取款操作的线程运行函数
    void* withdraw_fn(void *arg){
        OperArg *oa = (OperArg*)arg;
        //deposit存款函数
        double amt = deposit(oa->account,oa->amt);
        printf("%8s(0x%lx) deposit %f from account %d/n",oa->name,pthread_self(),amt,oa->account->code);
        return (void*)0;
    }
    //定义存款操作的线程运行函数
    void* deposit_fn(void *arg){
        OperArg *oa = (OperArg*)arg;
        //withdraw取款函数
        double amt = withdraw(oa->account,oa->amt);
        printf("%8s(0x%lx) withdraw %f from account %d/n",oa->name,pthread_self(),amt,oa->account->code);
        return (void*)0;
    }
    //定义查看银行账户的线程运行函数
    void* check_fn(void *arg){
        
    }
    int main(void){
        int err;
        pthread_t boy,girl;
        Account *a = create_account(10001,10000)//卡号,余额
            
        OperArg o1 , o2;//两个操作者
        strcpy(o1.name,"boy");//strcpy()把第二个参数值,放到第一个参数中
        o1.account = a;
        o1.amt = 10000;
        
        strcpy(o2.name,"girl");
        o2.account = a;
        o2.amt = 10000;
        
        //启动两个子线程(boy和girl)同时去操作同一个银行账户
        if((err = pthread_create(&boy,null,withdraw_fn,(void*)&o1)!=0){
            printf("pthread create error");
        }
        if((err = pthread_create(&girl,null,withdraw_fn,(void*)&o2)!=0){
            printf("pthread create error");
        }
        
        pthread_join(boy,null);
        pthread_join(girl,null);
           
        //查看账户余额
        printf("account balance %f/n",get_balance(a));
        //销毁账户
        destroy_account(a);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    程序输出结果:

    在这里插入图片描述

    girl和boy都从银行账户10001中取出了10000元,所以产生了错误。

    3.线程互斥----互斥锁(互斥量)
    • 互斥锁是一种简单的加锁的方法,来控制对共享资源的访问。在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行访问。若其他线程希望上锁一个已经被上了互斥锁的资源,则该线程挂起,直到上锁的线程释放互斥锁为止。
    • 互斥锁数据类型
      • pthread_mutex_t
    4.互斥锁创建和销毁
    //锁的定义
    pthreat_mutex_t  mutex;
    //锁的初始化
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthreat_mutex_t *mutexattr);
    //锁的销毁
    int pthread_mutex_destroy(pthreat_mutex_t *mutex);
    返回:成功返回 0  否则返回错误编号
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 参数
      • mutex :互斥锁
      • mutexattr:互斥锁创建方式 或者可以传入锁的属性后面会有
        • PTHREAD_MUTEX_INITIALIZER(默认) 创建快速互斥锁 第一次上锁成功,第二次上锁会阻塞
        • PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP 创建递归互斥锁 第一次上锁成功,第二次以后上锁还是成功,内部计数
        • PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP 创建检错互斥锁 第一次上锁成功,第二次上锁会出错
    5.互斥锁上锁和解锁
    int pthread_mutex_lock(pthread_mutex_t *mutex); //上锁  上不了锁该线程就阻塞
    int pthread_mutex_trylock(pthread_mutex_t *mutex); //上锁 上不了锁该线程就返回错误信息
    int pthread_mutex_unlock(pthread_mutex_t *mutex); //释放锁
    返回:成功返回 0 出错返回出错码
    
    • 1
    • 2
    • 3
    • 4
    • 参数
      • mutex :互斥锁
    6.线程互斥案例—ATM取钱–使用互斥锁

    头文件 account.h

    #ifndef __ACCOUNT_H__
    #define __ACCOUNT_H__
    #include 
    //银行账户结构体
    typedef struct{
        int code;//卡号
        double balance;//余额
        //定义一把互斥锁,用来对多线程操作
        //银行账户(共享资源)进行加锁保护
        /*
        	建议互斥锁和账户(共享资源)绑定一起,用来锁定一个账户(共享资源)
        	尽量不设置全局变量,否则肯能出现一把锁去锁几百个账户,导致并发性能降低
        */
        pthread_mutex_t mutex;
        
    }Account;
    //创建账户
    extern Account* creat_account(int code,double balance);
    //销毁账户
    extern void destory_account(Account *a);
    //取款
    extern double withdraw(Account *a,double amt);
    //存款
    extern double deposit(Account *a,double amt);
    //查看账户余额
    extern double get_balance(Account *a);
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    功能文件account.c

    #include "account.h"
    #include 
    #include 
    #include 
    
    //创建账户
    Account* creat_account(int code,double balance){
        //创建账户在堆当中
        Account *a = (Account*)malloc(sizeof(Account));
        assert(a != NULL);//用来判断a指针所需要开辟的空间,开辟成功了吗,成功就继续,否则就程序终止运行。
        a->code = code;
        a->balance = balance;
        //对互斥锁进行初始化
        pthread_mutex_init(&a->mutex,null);
        return a;
    }
    //销毁账户
    void destory_account(Account *a){
        assert(a != NULL);
        //销毁互斥锁
        pthread_mutex_destroy(&a->mutex)
        free(a);//释放空间
    }
    //取款
    double withdraw(Account *a,double amt){
        assert(a != NULL);//判断指针a是否为空
        /*
        	从成功上锁,到释放锁之间对共享资源操作的区间称为临界区
        */
        pthread_mutex_lock(&a->mutex);//对共享共享资源(账户)加锁
        //有线程执行到此时,账户加锁,当其他线程执行到此时,就会被堵塞
            
        if(amt < 0 || amt > a->balance){
            //释放互斥锁
            pthread_mutex_unlock(&a->mutex);
            return 0.0;
        }
        double balance = a->balance;
        sleep(1);
        balance -= amt;
        a->balance = balance;
        //释放互斥锁
        pthread_mutex_unlock(&a->mutex);
        return amt;
    }
    //存款
    double deposit(Account *a,double amt){
       assert(a != NULL);
       pthread_mutex_lock(&a->mutex)//对共享共享资源(账户)加锁
       if(amt < 0){
           //释放互斥锁
           pthread_mutex_unlock(&a->mutex);
           return 0.0;
       }
        double balance = a->balance;
        sleep(1);
        balance += amt;
        a->balance = balance;
        //释放互斥锁
        pthread_mutex_unlock(&a->mutex);
        return amt;
    }
    //查看账户余额
    double get_balance(Account *a){
        assert(a != NULL);
        pthread_mutex_lock(&a->mutex)//对共享共享资源(账户)加锁
        double balance = a->balance;
        //释放互斥锁
        pthread_mutex_unlock(&a->mutex);
        return balanec;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    执行文件 account_test.c

    #include "account.h"
    #include 
    #include 
    #include 
    #include 
    
    //操作者的结构体
    typedef struct{
        char  name[20];//操作者姓名
        Account *account;//账户
        double amt;//金额
    }OperArg
    //定义取款操作的线程运行函数
    void* withdraw_fn(void *arg){
        OperArg *oa = (OperArg*)arg;
        //deposit存款函数
        double amt = deposit(oa->account,oa->amt);
        printf("%8s(0x%lx) deposit %f from account %d/n",oa->name,pthread_self(),amt,oa->account->code);
        return (void*)0;
    }
    //定义存款操作的线程运行函数
    void* deposit_fn(void *arg){
        OperArg *oa = (OperArg*)arg;
        //withdraw取款函数
        double amt = withdraw(oa->account,oa->amt);
        printf("%8s(0x%lx) withdraw %f from account %d/n",oa->name,pthread_self(),amt,oa->account->code);
        return (void*)0;
    }
    //定义查看银行账户的线程运行函数
    void* check_fn(void *arg){
        
    }
    int main(void){
        int err;
        pthread_t boy,girl;
        Account *a = create_account(10001,10000)//卡号,余额
            
        OperArg o1 , o2;//两个操作者
        strcpy(o1.name,"boy");//strcpy()把第二个参数值,放到第一个参数中
        o1.account = a;
        o1.amt = 10000;
        
        strcpy(o2.name,"girl");
        o2.account = a;
        o2.amt = 10000;
        
        //启动两个子线程(boy和girl)同时去操作同一个银行账户
        if((err = pthread_create(&boy,null,withdraw_fn,(void*)&o1)!=0){
            printf("pthread create error");
        }
        if((err = pthread_create(&girl,null,withdraw_fn,(void*)&o2)!=0){
            printf("pthread create error");
        }
        
        pthread_join(boy,null);
        pthread_join(girl,null);
           
        //查看账户余额
        printf("account balance %f/n",get_balance(a));
        //销毁账户
        destroy_account(a);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    程序运行结果:

    在这里插入图片描述

    7.互斥锁属性创建和销毁
    //互斥锁属性的初始化
    int pthread_mutexattr_init(pthread_mutexattr_t *attr);
    //互斥锁属性的销毁
    int pthread_mutexattr_destroy(pthread_mutexattr_t *attr)
    
    • 1
    • 2
    • 3
    • 4
    • 参数
      • attr:互斥锁属性定义
    8.互斥锁属性之一—进程共享属性操作
    //restrict用于限定和约束指针,表示这个指针只访问这块内存的唯一方式
    //共享属性获取,pshared用于存放获取的共享属性
    int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr,int *restrict pshared);
    //共享属性设置,pshared用于指定共享属性
    int pthread_mutexattr_setpshared(const pthread_mutexattr_t *attr,int pshared);
    返回:成功返回0,出错返回错误编号
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 参数
      • attr:互斥锁属性
      • pshared:进程共享属性
        • PTHREAD_PROCESS_PRIVATE(默认情况) 锁只能用于一个进程内部的两个进程进行互斥
        • PTHREAD_PROCESS_SHARED 锁可以用于两个不同进程中的线程进行互斥
    9.互斥锁属性之一----互斥锁类型操作
    //restrict用于限定和约束指针,表示这个指针只访问这块内存的唯一方式
    //共享属性获取,pshared用于存放获取的共享属性
    int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type);
    //共享属性设置,pshared用于指定共享属性
    int pthread_mutexattr_settype(const pthread_mutexattr_t *attr,int type);
    返回:成功返回0,出错返回错误编号
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 参数
      • attr:互斥锁属性
      • type:互斥锁类型
        • 标准互斥锁:PTHREAD_MUTEX_NORMAL 第一次上锁成功,第二次上锁会阻塞
        • 递归互斥锁:PTHREAD_MUTEX_RECURSIVE 第一次上锁成功,第二次以后上锁还是成功,内部计数
        • 检错互斥锁:PTHREAD_MUTEX_ERRORCHECK 第一次上锁成功,第二次上锁会出错
        • 默认互斥锁:PTHREAD_MUTEX_DEFAULT(同标准互斥锁)
    10.代码了解互斥锁属性
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc,char *argv[]){
        //定义锁
        pthread_mutex_t mutex;
        
        if(argc<2){
            printf("-usage:%s [error|normal|recursive]/n",argv[0]);
            exit(1);
        }
        
        //定义互斥锁属性
        pthread_mutexatte_t  mutexattr;
        //初始化互斥锁属性
        pthread_mutexattr_init(&mutexattr);
        
        /*
        	strcmp函数用于比较两个字符串并根据比较结果返回整数。
        	基本形式为strcmp(str1,str2),
        	若str1=str2,则返回零;
        	若str1str2,则返回正数。
        */
        if(!strcmp(argv[1]),"error"){
            //设置互斥锁类型---检错型
            pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_ERRORCHECK);
        }else if(!strcmp(argv[1],"normal")){
            //设置互斥锁类型---标准型
            pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_NORMAL);
        }else if(!strcmp(argv[1],"recursive")){
            //设置互斥锁类型---递归型
            pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_RECURSIVE );
        }
        //锁的初始化,第一个参数传入锁,第二个参数可以指定,也可以传入锁的属性,
        pthread_mutex_init(&mutex,&mutexattr)
        //上锁
        if(pthread_mutex_lock(&mutex) !=0){
            printf("lock failure\n");
        }else{
             printf("lock sucess\n");
        }
        //第二次上锁
        if(pthread_mutex_lock(&mutex) !=0){
            printf("lock failure\n");
        }else{
             printf("lock sucess\n");
        }
        
        //上了两次锁,解锁两次
        pthread_mutex_unlock(&mutex);
        pthread_mutex_unlock(&mutex);
        
        //销毁属性
        pthread_mutexattr_destroy(&mutexattr);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    程序运行结果:

    ​ 选择normal,锁的类型是普通型,第一次上锁成功,第二次失败,程序阻塞

    ​ 选择recursive,锁的类型是递归性,第一次上锁成功, 第二次也成功,只是内部计数。

    ​ 选择error,锁的类型是检错型,第一次上锁成功,第二次失败,程序报错
    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    Ubuntu搭建Mqtt服务器
    重温 JavaScript 系列(1):Object.assign、iframe、HashMap、手写 instanceof
    【21天算法挑战赛】排序算法——冒泡排序
    Covariance Estimators协方差估计大比拼:性能、应用场景和可视化对比总结
    【分布式存储】聊一下分布式存储中分片机制
    调用线程的run()和start()方法有什么不同呢?
    vue3使用ts封装axios
    农牧行业全产业链20+业务用契约锁电子签,释放成本、提效90%
    wireshark抓包本地IDEA xml格式报文教程以及postman调用接口
    STM32的标准库及其使用
  • 原文地址:https://blog.csdn.net/kaszxc/article/details/128049575