• 线程(中):线程安全


     

     "拉住童年的手,进行告别"


     上篇主要谈论了,如何使用pthread库,进行线程的创建、等待....

    (一)线程安全

    (1)互斥的相关概念

    临界资源:多线程执行流共享的资源就叫做临界资源

    临界区:每个线程内部,访问临界自娱的代码,就叫做临界区
    互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源.
    原子性: 不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完

    我们先在实现一个抢票线程,可以让我们更好的理解,线程安全的一系列问题。

    然而当票抢成负数是我们不希望看到的! 

    对于一张共享资源的ticket票,任何执行流,都能看到。因此这个ticket也叫做临界资源

    很显然,这样一种区域,是不安全的。因为任何执行流都可以在任何情况下对其进行更改。

    从而导致,不同执行流拿到的数据是不一样。

    由图可见,不管是ticket++ 还是ticket--,难以保证原子性。出现各执行流拿到的数据不一致的情况!

    (2)互斥锁;

    正因如此,就需要程序员,自己去保证线程运行,是安全的的情况。让各个执行流达到一种共同遵守,一次只有一个执行流,看到共享资源(临界资源)。

    ①这也就是我们谈到的加锁与解锁

           #include

           int pthread_mutex_lock(pthread_mutex_t *mutex);
           int pthread_mutex_trylock(pthread_mutex_t *mutex);
           int pthread_mutex_unlock(pthread_mutex_t *mutex);

    这样就可以保证,只一个执行流进入临界区,其余执行流会因为没拿到锁,阻塞在锁外。 

    ②如何理解,锁是每个执行流都可以去争抢的资源,那么也叫做临界资源,它又如何能保护临界资源?

    本质上,锁需要被保护,才能去保护临界资源。而本质上,锁的实现,满足原子性的特性,是能够保证自己的安全。

     

    锁的其他接口用法; 

          #include

           销毁;

           int pthread_mutex_destroy(pthread_mutex_t *mutex);
           初始化;

           int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                     const pthread_mutexattr_t *restrict attr);
           pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

     

     

     

    (3)可重入函数 vs 线程安全

    线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。

    可重入函数:一个执行流还没有执行完,就有其他执行流进来。但运行结果不会发生改变。 

     ①可重入与线程安全的联系

    1.函数是可重入的,那就是线程安全的.
    2.函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题.
    3.如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的.

     

    ②可重入与线程安全的区别

    1.可重入函数是线程安全函数的一种

    2.线程安全不一定是可重入的,而可重入函数则一定是线程安全的

     

    (4)死锁

    ①死锁的原因

     执行流占用资源不释放,从而处于一种永久等待的状态;

     

    ②死锁的必要条件; 

    1.互斥条件

    2.请求保持

    3.不剥夺条件

    4.循环等待


     

    (二)线程安全2

    (1)同步的相关概念

    可以知道,一个线程互斥地访问临界资源的时候,其他线程除了等待该线程访问完临界资源,释放锁之外,什么也做不了。

    如何理解进程等待队列?

    本质就是把进程、线程放入等待队列, 把它们的运行状态 设置R —>S,挂起等待。

    在用户视角来看,就是被阻塞。

    为什么需要存在同步?

    互斥锁需要各个执行流进行争抢得到。难免会有竞争能力强的执行流,反复申请锁,释放锁,但却不会做什么有效的事情,致使其他竞争力相对弱的执行流无法拿到锁,处于一饥饿状态!

    所谓同步:保证数据安全的前提下,让线程 进行按序等待 访问临界资源~

    有这个能力处理 线程同步问题的,叫做 条件变量。

    (2) 条件变量;

    ①初始化

           #include
           int pthread_cond_init(pthread_cond_t *restrict cond,
                  const pthread_condattr_t *restrict attr);
           pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    cond:要初始化的条件变量

    attrNULL

    ②销毁

     #include

     int pthread_cond_destroy(pthread_cond_t *cond); 

    ③等待条件

    #include         

    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

    cond:在这个条件变量上等待;

    mutex;互斥量(进行 释放)否则 别的线程进不来

    ④唤醒等待 

           #include

           int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有在等待的线程
           int pthread_cond_signal(pthread_cond_t *cond);  //唤醒单个 出在序列前列的进程

     


    (三)生产者消费者模型;

    ①什么是生产者消费者模型?

     三种关系 两种角色 一个场所;“遵循的321”原则;

    当然生产消费者模型还有其他优点:
    ①支持并发

    ②支持忙闲不均.

    1.关系;

    生产者生产者:竞争 

    消费者消费者:竞争

    消费者生产者:互斥+同步

    2.交易场所:内存中的一份缓冲区

    (1)BlockQueue

    ①基本结构

    ②主体逻辑

     

    ③条件判断与初始化

     

     


    (四)POSIX信号量

     在进程间地址通信的时候提到过信号量,但是是在 system V提过的。

    他们作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。
    特别的,POSIX信号量 也可以用于线程同步

    (1)什么是信号量? 

    在前面章节也已经说过,所谓信号量,本质就是一个计数器。描述这临界资源的内容。

    其作用,就是为了实现同步互斥+更细腻度 对临界资源进行管理!

    如何理解申请到信号量 与 申请信号量

    申请到 意味着 拥有使用 这份临界资源的权限!

    申请信号量 意味着 信号量里的计数器 -- ;反之 就 ++

    (2)信号量使用

    ①初始化信号量 

    #include
    int sem_init(sem_t *sem, int pshared, unsigned int value);
    参数:
    pshared:0 表示线程间共享,非零表示进程间共享
    value :信号量初始值

    ②销毁信号量

    int sem_destroy(sem_t *sem);

    ③等待信号量 ​​​​​

    功能:等待信号量,会将信号量的值减 1
    int sem_wait(sem_t *sem);

    ④发布信号量  

    功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加 1
    int sem_post(sem_t *sem);

     

    模拟用信号量,实现一个互斥锁!(sem = 1)

    框架; 只需要让把sem 设置为1 

    执行任务;

     

     


    总结:

    ①保证线程安全的两步:同步+互斥

    ②互斥:pthread_mutex_t lock 互斥锁

    同步:pthead_cond_t cond 条件变量

    ④POSIX信号量


    本篇就到此结束 感谢你的阅读

    祝你好运~

     

  • 相关阅读:
    基于晶体结构优化的BP神经网络(分类应用) - 附代码
    1、自然语言和单词的分布式表示(上)
    Unity手机游戏发热发烫优化指南与技巧
    JavaScript Math对象常用方法
    基于hexo框架快速从0到1搭建个人博客----评论功能(二)
    什么叫非理性投资
    同花顺平台指标源码 【控盘度指标线】
    ZooKeeper(一):基础介绍
    Matplotlib入门[01]——Pyplot
    第六章-项目进度管理
  • 原文地址:https://blog.csdn.net/RNGWGzZs/article/details/126220438