当前位置: 首页 > article >正文

Linux学习之路 -- 线程 -- 死锁及线程安全相关问题

在上文中,我们已经介绍了线程池的编写,下面补充一下线程的相关知识。

目录

1、线程安全与可重入

<1>概念

<2>区别联系

<3>常见线程不安全的情况

<4>常见的不可重入情况

2、死锁问题

<1>死锁概念

<2>死锁四个必要条件

<3>解决办法

<4>检测与避免死锁

3、其他类型锁的介绍

<1>悲观锁

<2>乐观锁

 <3>自旋锁

<1>引例

<2>概念及使用

4、读者写者问题


1、线程安全与可重入

<1>概念

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

重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。(一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数)

<2>区别联系

线程安全是线程在执行中的相互关系,而可重入是函数的一种特点。引起线程安全问题的方法有很多,不可重入是其中的一种。只要是不可重入的函数,一般就是线程安全的。

<3>常见线程不安全的情况

1、不保护共享变量的函数
2、函数状态随着被调用,状态发生变化的函数
3、返回指向静态变量指针的函数
4、调用线程不安全函数的函数

<4>常见的不可重入情况

1、调用了malloc/free函数,因为maloc函数是用全局链表来管理堆的
2、调用了标准I/0库函数,标准I/0库的很多实现都以不可重入的方式使用全局数据结构
3、可重入函数体内使用了静态的数据结构

2、死锁问题

<1>死锁概念

死锁其实就是当多线程在执行同一段代码时,对锁的不合理使用导致每个线程都无法获取锁,从而使得线程处于一种永久阻塞的状态。(如下图)

<2>死锁四个必要条件

1、互斥条件:一个资源每次只能被一个执行流使用
        这个条件简单来说就是要死锁,首先一定要有锁。
2、请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
        这个条件简单来说,就是一个线程持有了一个锁,还想要另一个锁,同时自己又不愿意放弃自己持有的锁。
3、不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺
        这个条件其实就是当一个线程持有锁时,没有用完前,不能强行剥夺锁。
4、循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
        这个条件会稍微抽象一点,不过也好理解。就是线程A持有锁1,线程B持有锁2,在此基础上线程A等待锁2,线程B等待锁1.

<3>解决办法

既然我们已经知道了死锁产生的四个必要条件,那么要避免死锁的产生,只要破坏其中一个条件即可。
        1、互斥条件:要破坏该条件其实就是尽量不使用锁,这样肯定就不会有死锁问题。
        2、请求与保持条件:要破坏该条件,可以让一个线程持有一把锁的情况下(线程需要两把锁),如果继续申请锁失败就直接释放原来的锁,重新取竞争两把锁,也就是放弃保持条件。
        3、不剥夺条件:该条件其实不建议破坏,因为一旦破坏可能造成许多问题。如果实在想要破坏,就在申请锁失败后,强行释放掉该锁
        4、循环等待条件:该条件的破坏其实是最容易的,我们只需要让不同线程在竞争锁时同步即可,也就是多个线程在一定时间段内只竞争同一把锁。

<4>检测与避免死锁

在linux中我们可以使用pstack命令对线程进行查看,该命令显示每个线程的栈跟踪信息(函数调用过程)。该命令的使用方式也很简单,只要pstack + pid即可。在检测死锁问题时,我们可以多次调用pstack命令,多次对比,看看有哪些线程是在多次等锁,而且一直没有什么变换,那么这大概率就是死锁问题导致的。

其中还有避免死锁的检测算法,比较著名的就是银行家算法,该算法在许多博客上都有介绍,这里就不再赘述,当然也还有其他的死锁避免的办法,这里就不详细地介绍了。

3、其他类型锁的介绍

<1>悲观锁

概念: 悲观锁假设在数据被访问的时候,极有可能有其他事务同时修改数据,因此在整个数据处理过程中,将数据锁定,直到事务完成。

特点:

  • 保守:它总是假设最坏的情况,认为每次访问数据时都会产生冲突。
  • 阻塞:如果一个事务持有了锁,其他尝试获取锁的事务将会被阻塞,直到锁被释放。
  • 重试:被阻塞的事务在锁释放后需要重新尝试获取锁。

<2>乐观锁

概念: 乐观锁假设在数据被访问的时候,不会有其他事务同时修改数据,因此在数据修改时并不进行锁定,而是在更新数据的时候检查是否有其他事务同时修改了数据。

特点:

  • 乐观:它假设最好的情况,认为在数据被访问期间不会发生冲突。
  • 非阻塞:事务在读取数据时不会锁定资源,只有在更新数据时才会检查冲突。
  • 冲突检测:在更新数据时,通过版本号、时间戳或者业务逻辑来检测是否有其他事务同时修改了数据。

乐观锁与悲观锁涉及数据库的知识,这里就不详细介绍,简单了解即可。

 <3>自旋锁

在了解自旋锁之前,我们需要先理解自旋的概念,下面用一个例子来帮助理解自旋。

<1>引例

现在有一对好朋友,他们分别叫张三和李四,其中张三是个学渣,而李四是个学霸。到了期末前一天,张三找到李四复习高数。当张三来到李四的宿舍楼楼下,给李四打了个电话,叫李四下来。李四回复说:“我也还在复习,估计还要一两个小时,等我复习完就下来找你”。张三一听李四还要那么久,就决定先到网吧玩一阵子再回来。一个多小时后,李四告诉张三可以过去找他了,此时张三就离开网吧去找李四复习了。

考完高数后,张三又打听到明天要考离散,于是又去找李四复习。当张三来到李四的宿舍楼楼下,给李四打了个电话,叫李四下来。李四回复说:”离散我复习完了,我马上就下来找你“。张三一听李四马上就下来,张三也就并没有去网吧玩了,而是等待李四下来。在李四下来之前,张三反复给李四打电话,确认李四到哪里了。过了一会后,李四下楼后找到了张三,李四和张三一起学习去了。

在上面两个例子中,其实张三就是线程,而李四就是一把锁(资源),网吧就类似于条件变量。在第二个例子中,张三不断打电话向李四确定的过程就是自旋。而在这个过程中造成张三等待方式的差异的原因,其实就是李四(资源)准备就绪的时间。

其实在线程等待锁的过程就是像上述的过程一样,如果时间比较长,线程会被阻塞挂起,等待临界区执行完并释放锁后,再被唤醒去竞争锁。而如果等待的时间比较短,那么线程就不必阻塞挂起,而是一直检测锁是否就绪。

<2>概念及使用

概念:自旋锁是一种让线程在获取锁时不断循环检查锁状态的锁机制,而不是进入休眠状态。

接口:

头文件均为pthread.h 

使用方式和pthread_mutex_t 基本是一致的,区别不大。不过这里需要介绍一下pthread_spin_init接口的pshared参数,这个参数主要用来表示自旋锁是否共享,一般又以下两个选项

  1. PTHREAD_PROCESS_PRIVATE:

    • 当 pshared 设置为 PTHREAD_PROCESS_PRIVATE 时,锁只能被创建它的进程中的线程访问。
    • 这意味着锁是私有的,不能在进程间共享。
    • 这是 pshared 参数的默认值。
  2. PTHREAD_PROCESS_SHARED:

    • 当 pshared 设置为 PTHREAD_PROCESS_SHARED 时,锁可以被多个进程中的线程访问,只要这些进程可以访问到锁所在的内存区域。
    • 这通常意味着锁存储在共享内存区域中。
    • 使用这个值时,需要确保所有访问共享锁的进程都有正确的访问权限。

4、读者写者问题

相较于生产者消费者模型,这个模型在我们日常生活中反而是最常见的,比如我们写文章,发视频等。

这个模型也和生产消费模型一样,具有两个对象,一个交易场所和三种关系。其中两个对象就是读者和写着,三种关系就是写者与读者,写者与写者,读者与读者的关系。写者和写者之间需要保持互斥的关系,读者和写者之间需要保证同步与互斥的关系,读者和读者之间没有关系。因为读者仅仅只是读数据,不对数据进行修改,所以读者之间没有什么关系,这也是和生产消费模型不同的点。

加锁逻辑(伪代码)

相关的接口

上述的上锁逻辑,OS已经帮助我们实现了,我们只需要使用上述的接口,就可以达到上述伪代码的效果。上述各种接口的逻辑和我们之前介绍的互斥锁几乎一致,所以这里就不详细介绍了。需要注意的是,rdlock对应的就是上述伪代码中的读者逻辑,wrlock对应的就是上述伪代码中写者逻辑。

读者优先

如果当前我们读者和写者一起来的时候,并且读者不断到来,读者也没有退完。此时,肯定是让读者先访问共享的资源。

写者优先

读者在写者来临之前可以正常访问资源,一旦写者来了,后续的读者就不能进入了。这个做法比较复杂,不过也是可以通过读写锁实现的,这里不作介绍。

以上就是所有内容,如果想要深入了解文中的内容,可以依据文中介绍的点自行搜索。文中如有不对支持,还望各位大佬指正,谢谢!!!


http://www.kler.cn/news/331468.html

相关文章:

  • centos一些常用命令
  • 计算机网络:计算机网络体系结构 —— OSI 模型 与 TCP/IP 模型
  • 蓝桥杯【物联网】零基础到国奖之路:十八. 扩展模块之光敏和AS312
  • 7.3树形查找
  • 国庆刷题(day2)
  • Redis-哨兵
  • C++ 语言特性10 - 委托构造函数
  • QQ机器人搭建
  • iframe标签是做什么用的
  • 计算机毕业设计 Java酷听音乐系统的设计与实现 Java实战项目 附源码+文档+视频讲解
  • map部分重点
  • <数据集>工程机械识别数据集<目标检测>
  • FBX福币历史重演,ETH可能会在第四季度出现熊市
  • mysql安装及使用·1
  • 计算机毕业设计Python抖音可视化 抖音大数据分析 抖音爬虫 抖音用户行为分析 抖音大数据 Hadoop Spark 数据仓库 推荐系统 机器学习 深度学习
  • [Unity Demo]从零开始制作空洞骑士Hollow Knight第十二集:制作完整地图和地图细节设置以及制作相机系统的跟随玩家和视角锁定功能
  • 在线JSON可视化工具--支持缩放
  • 华为云技术深度解析:以系统性创新加速智能化升级
  • map_set的使用
  • 【Windows】 C++实现 Socket 通讯