线程安全(互斥锁)

 线程安全 (这些接口都是C库实现的)
    黄牛抢票(黄牛---线程, 票 --- 临界资源) 两个黄牛抢到同一张票, 
    1. 线程安全指的是多个线程同时运行访问临界资源, 不会导致程序的结果产生二义性,
        临界资源: 在同一时刻, 该资源只能被一个执行流所访问, 涉及临界资源的区域 --> 临界区
        访问: 在临界区当中对临界资源进行非原子操作,意味着可以被打断
        (原子操作是一步完成的,当前操作只有两个结果, 要不完成, 要不未开始)
    2. 如何保证线程安全
        互斥: 保证在同一时刻只有一个执行流访问临界资源
            互斥锁:  (这个锁是在堆上开辟的, 而这个堆也是在PCB中的 属于task_struct结构体中 虚拟地址空间的堆)
  !!!!!!    一定要在创建线程之前去初始化互斥锁, 这样就可以在线程内部使用互斥锁了, 当线程退出后, 需要释放互斥锁资源
  !!!!!!    加锁一定是要在访问临界资源之前加的,  拿到锁资源之后, 再去访问临界资源

                1. 使用互斥锁来保证互斥属性   (他维护他自己的计数器)
                2. 互斥锁本质上是一个计数器, 但是这个计数器只有两个取值, 一个为0,一个为1,
                    0代表: 无法获取互斥锁
                    1代表: 可以获得互斥锁
                3 在访问临界资源前, 先获取互斥锁 
                    如果能够获取到互斥锁, 表示当前资源可以去访问
                    如果不能获取到互斥锁, 表示当前资源不可以去访问, 也就是意味着计数器当中的值为0, 意味着已经有一个线程正在访问临界资源
                4. 使用流程:
                    1. 初始化互斥锁
                    2. 加锁(将计数器当中的值变成0)
                        如果计数器为1, 可以正常加锁
                        如果计数器为0, 不可以正常加锁, 当前想要加锁的执行流被阻塞
                    3. 访问临界资源
                    4. 解锁(将计数器当中的值变成1)
                5. 操作接口
                    1. 定义互斥锁
                        pthread_mutex_t:互斥锁变量类型
                    2. 初始化互斥锁
                        int pthread_mutex_init(pthread_mutex_t* mutex, pthread_mutexattr_t* attr)  (动态初始化)
                            mutex: 互斥锁变量, 要初始化哪一个互斥锁变量, 一般情况下, 定义互斥锁对象, 传入互斥锁对象的地址
                            attr: 互斥锁的属性, 一般情况下, 我们直接置为NULL, 采用默认属性
                    课后调研: 为什么可以直接这样赋值就初始化了????????????????? 查看源码 查看定义
                    还有一种初始化方式为赋值初始化
                    pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER(也叫静态初始化)
                    3. 加锁
                        int pthread_mutex_lock(pthread_mutex_t* mutex) ----> 阻塞加锁操作
                            mutex: 要对哪一个互斥锁进行加锁, 传递互斥锁对象的地址即可
                            该接口为阻塞接口, 如果加锁成功则返回, 如果加锁失败, 直到加锁成功

                        这是一个带有超时的加锁接口                 
                        int pthread_mutex_timedlock(pthread_mutex_t* mutex, const struct timespec* abs_timeout)
                            mutex: 要对哪一个互斥锁进行加锁, 传递互斥锁对象的地址即可
                            abs_timeout: 加锁超时时间, 在加锁的时候, 如果超过超时时间还没有加上锁, 则直接返回, 会报错, 报错TIMEOUT, 不会进行阻塞等待
                            struct timespec: 有两成员, 第一个变量为秒级, 第二个为纳秒

                        int pthread_mutex_trylock(pthread_mutex_t* mutex)   ----> 非阻塞加锁操作
                            mutex: 传入互斥锁的地址, 来进行加锁操作
                                如果计数器值为1, 意味着可以加锁, 加锁操作后, 对计数器当中只从1改变成为0
                                如果计数器值为0, 一位这不可以加锁, 该接口直接返回, 不进行阻塞等待, 返回EBUSY, 表示拿不到加锁资源

                    4. 解锁操作:
                        int pthread_mutex_unlock(pthread_mutex_t* mutex)
                            不管使用pthread_mutex_lock/pthread_mutex_trylock/pthread_mutex_timedlock三种哪种接口加锁, 都可以使用unlock接口都可以解锁
                    如果不解锁, 导致阻塞等待锁资源 , 不解锁就会使执行流陷入阻塞, 在二次对互斥锁加锁时, owner线程也进入等待
                        thread apply all bt:  调试的时候使用这个命令 可以查看所有线程的调用堆栈
                    如果不释放互斥锁, 则会阻塞其他线程, 我们称之为死锁
                    互斥锁只能被自己线程(谁拿到锁资源)释放, 别的线程都释放不了
                        什么时候解锁呢???????????????????????????????????????????????????????
                           若只在入口函数退出是加入解锁, 导致线程用完互斥锁之后, 程序执行后, 第一个工作线程把锁拿走了, 未解锁, 且退出了(类似偷走了), 其他线程进行不了解锁操作, 计数器的值依旧为0, 导致阻塞
                        所以应在所有可能退出的地方都要加上解锁操作,
                    5. 销毁互斥锁变量
                        int pthread_mutex_destory(pthread_mutex_t* mutex)
                    CPU是操作系统去管理的, 其他程序分时间在CPU上运行

                问题: 对于加锁操作, 将计数器当中的值从1变成0, 为什么是原子操作?
                    这个互斥锁的计数器本身就是C库在维护的, 计数器本身也是一个变量, 这个变量保存了1/0; 根据冯诺依曼体系结构, 内存和CPU, 寄存器的关系, 进行加锁时 需要CPU去判断是否可以加锁, 计数器(mutex_val)的值保存在内存中, 不管计数器的值是怎样, 只有加锁都给寄存器中放一个0, 加锁时交换寄存器与内存的值, xchgb(汇编中的指令), 这个交换操作是原子性的, 在交换结束后, 接下来只需要去
                    判断寄存器中的值是否为1, 来判断是否可以加锁, 
                    如果寄存器中的值为1, 则表示之前内存中计数器的值为1, 表示可以加锁, 
                    如果为0, 则表示之前内存中计数器的值为0, 表示不可以加锁; 
                    主要是由于这个交换操作保证了原子性.

                    上锁: 第一步是将寄存器的值变为0, 第二步将寄存器当中的值与内存的值交换, 接着判断寄存器当中的值是否大于0, 如果大于0 , 直接返回, 说明lock这个函数返回了, 当等于0时 进行上锁, 当寄存器中的值为0时, 上锁成功
                    解锁时, 将内存中计数器的值变为1 (每句汇编代码都保证了原子性, 但是我们所写的指令则可能是多条汇编代码, 而被调度的时候是一条指令还执行完, 返回时从上次中断的汇编代码的下一条代码开始 恢复现场)
                    
            死锁: 
                1. 什么是死锁?
                    程序当中有哟个执行流没有释放锁资源, 会导致其他想要获取该所的执行流陷入阻塞等待, 这种情况被称为死锁
                    程序当中每一个执行流都占有一把互斥锁, 但是由于各个执行流在占有互斥锁(已经获取)的情况下, 还想申请对方的锁(其他执行流所占有的互斥锁), 这种也会导致死锁  ------ 吃着碗里的,看着锅里的, 谁申请谁阻塞


        条件变量 --> 同步: 保证程序对临界资源访问的合理性

        信号量 --> 既能保证同步 也能保证互斥

你可能感兴趣的:(linux知识点,linux)