【linux】线程 (三)

 

 13. 常见锁概念

(一)了解死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程占有的,且不释放的资源,而处于的一种永久等待状态

(二)死锁四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

(二)避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致 (不是交错申请不同的锁资源)
  • 避免锁未释放的场景
  • 资源一次性分配

14. linux线程同步

(一)条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了,为了使抢夺锁资源更公正(防止饥饿问题),就需要把所有等待锁资源的线程放入一个等待队列,直到线程被唤醒

。这种情况就需要用到条件变量

注意:

条件变量必须依赖锁

(二)同步概念

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步

(三)条件变量相关函数

条件变量函数 初始化

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict

attr);

参数:

cond:要初始化的条件变量

attr:条件变量的属性,一般设置成NULL

销毁

int pthread_cond_destroy(pthread_cond_t *cond)

参数:要销毁的条件变量函数

等待条件满足

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

参数:

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

mutex:互斥量

注意:

参数中有互斥量,是因为实现同步与互斥,需要先加锁,再判断(判断也是访问临界资源,和加锁保护线程安全对应)是否进入等待队列休眠,而这个函数里面有互斥量是为了把锁资源释放,接下来访问临界资源的顺序是按照队列里面的顺序进行的(前提是线程被唤醒)

大概代码思路

pthread_mutex_lock(&mutex); // 加锁

pthread_cond_wait(&cond); //放入等待队列

pthread_mutex_unlock(&mutex); //解锁

注意:

  1. 唤醒可以由其它线程去唤醒,如主线程
  2. 三个函数的顺序不要更换(解锁和等待都不是原子操作)

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);

唤醒队列中的所有线程

int pthread_cond_signal(pthread_cond_t *cond);

按顺序唤醒队列中的一个线程

注意:

  1. 唤醒之后的线程需要申请锁资源
  2. 唤醒的时候,可能出现唤醒错误的情况。比如在生产者消费者模型中(多生产者多消费者容易出现这种错误),唤醒了多个生成者,并且消费者此时也是属于唤醒状态,导致生产者和生产者之间,消费者和生产者之间竞争同一把锁,锁最后的分配,可能导致临界资源访问条件不满足(唤醒之后拿到锁资源的线程,有可能这个时候的访问条件不满足,但依然会执行后面的代码),出现错误

正确代码思路

pthread_mutex_lock(&mutex); // 加锁

while(... ...) //判断访问资源的条件是否成立

{

pthread_cond_wait(&cond); //放入等待队列

}

pthread_mutex_unlock(&mutex); //解锁

注意:

判断用while,是为了保证,线程被唤醒(出了等待队列),并且竞争到了锁资源后,再一次判断资源条件是否成立,才能决定后面的代码能否执行

15. 生产者消费者模型

(一)使用生产者消费者模型的原因

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯

生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列;消费者不找生产者要数据,而是直接从阻塞队列里取

阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力,这个阻塞队列就是用来给生产者和消费者解耦的

(二)生产者消费者模型优点

  • 解耦
  • 支持并发
  • 支持忙闲不均

(三)基于BlockingQueue的生产者消费者模型

BlockingQueue

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出

(四)对生产者消费者模型的总结

1. 有三种关系

  • 生成者和生成者之间的关系:互斥
  • 生产者和消费者之间的关系:互斥,同步
  • 消费者和消费者之间的关系:互斥

2. 有两者角色

生产者和消费者

3. 有一种交易场所

特定结构的内存空间

16. POSIX信号量

POSIX信号量和 SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步

17. 基于环形队列的生产消费模型

(一)了解基于环形队列的生产消费模型

环形队列采用数组模拟,用模运算来模拟环状特性

判断队列是否为空或者为满,我们交给信号量去处理

注意:

  1. 信号量的本质是一个计数器,用来描述临界资源的数目(这里不把临界资源当成一个整体,而是很多份,多个线程并发访问临界资源中的不同份)
  2. 临界资源是否就绪的判断是在临界区之外的(和前一个生产消费模型区分开来),在申请信号量的时候,就已经间接在做判断了(这一步是预定操作,即预定成功,则整个临界资源必有一份资源是属于预定成功的线程的)

(二)基于环形队列的生产消费模型的原理

 

18. 线程池

(一)了解线程池

线程池:

一种线程使用模式

线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程(提前开辟好的),等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度

池化计数:

线程会用到池化技术(以空间换时间),大致操作(C++为例):现在C++类内创建线程(创建的线程当作类内成员变量),再利用原生线程去实现生产者消费者模型

注意:

  1. 在类内创建时,若线程执行的函数也是类内函数,会出现错误(因为执行的函数传参只有一个void*,而类内函数会多一个this指针),所以该执行函数必须是静态的
  2. 若执行函数是静态的,同样会出现一个问题,即静态成员函数无法访问非静态成员变量(没有this指针),所以执行函数的参数void*必须是this指针,通过穿过来的this指针,访问非静态成员变量

(二)线程池的应用场景

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。因为单个任务小,而任务数量巨大, 但对于长时间的任务,线程池的优点就不明显了
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限

(三)线程池示例

  1. 创建固定数量线程池,循环从任务队列中获取任务对象
  2. 获取到任务对象后,执行任务对象中的任务接口

19. 线程安全的单例模式

(一)什么是单例模式

单例模式是一种 "经典的, 常用的, 常考的" 设计模式(这个类的对象,只允许创建一个)

(二)什么是设计模式

针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式

(三)单例模式的特点

  1. 某些类, 只应该具有一个对象(实例), 就称之为单例
  2. 在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据

(四)饿汉实现方式和懒汉实现方式

举例类比(洗完的例子)

饿汉方式:

吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭

懒汉方式:

吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式

注意:

懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度(在准备进程启动时,需要把一些数据加载到内存,这一个过程的时间少)

饿汉方式 代码实现

template <typename T>
class Singleton 
{
static T data;
public:
static T* GetInstance() 
{
return &data;
}
};

data 是静态类内成员,会在进程启动之前,早就创建好,而不是等到启动进程时,再去创建

 

懒汉模式 代码实现

template <typename T>
class Singleton 
{
static T* inst;
public:
static T* GetInstance() 
{
if (inst == nullptr) 
{
inst = new T();
}
return inst;
}
};
template <typename T>
T* Singleton<T>:: inst = nullptr;

inst指针是要等待使用的时候,所指向的内容才会在堆上开辟新空间

(五)懒汉方式实现单例模式(线程安全版本)

代码

static phread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
template <typename T>
class Singleton 
{
static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
public:
static T* GetInstance() 
{
if (inst == NULL) //这一次的判断可以提高效率,使得没有进入第二个判断的线程,直接执行非临界资源的代码
{ 
lock.lock(); // 使用互斥锁, 防止多个线程进入第二个判断,保证多线程情况下也只调用一次 new
if (inst == NULL) 
{
inst = new T();
}
lock.unlock();
}
    
return inst;
}
};

T* Singleton<T>:: inst = nullptr;

注意:(代码不是完整的)

lock(),unlock()底层都是用原生线程库封装了

20. STL , 智能指针和线程安全

(一)常见问题

  • STL中的容器是否是线程安全

不安全

原因:

STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.

而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).

因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.

  • 智能指针是否是线程安全
  1. 对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.
  2. 对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.

(二)其他常见的各种锁

  1. 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起
  2. 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作
  3. CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
  4. 自旋锁(是否采用自旋锁,取决于临界资源访问的时间的长短,时间短,可以采用自旋锁,即线程不挂起,一直申请所资源

21. 读者写者问题

读写锁

在编写多线程的时候,有一种情况是十分常见的:有些公共数据修改的机会比较少,相比较改写,它们读的机会反而高的多。

读写锁可以专门处理这种多读少写的情况

注意:写和写竞争,写和读竞争且同步,读和读共享资源,读锁优先级高

相关函数

初始化

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t

*restrict attr);

销毁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加锁和解锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //写与写竞争

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); //读与写竞争

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

代码(部分)

void * reader(void * arg)  //读端
{
char *id = (char *)arg;
while (1) 
{
pthread_rwlock_rdlock(&rwlock);
if () //判断
{
pthread_rwlock_unlock(&rwlock);
break;
}
pthread_rwlock_unlock(&rwlock);
}
return nullptr;
}

void * writer(void * arg) //写端
{
char *id = (char *)arg;
while (1) 
{
pthread_rwlock_wrlock(&rwlock);
if () //判断条件
{
pthread_rwlock_unlock(&rwlock); 
break;
}

}
return nullptr;
}

中间一些过程省略了

底层实现(伪代码)

int reader_count = 0;
mutex_t rlock,wlock;
读端:
lock(&rlock)
reader_count++;
if(reader_count == 1) //第一个竞争到所资源的读端
{
    lock(&wlock); //写段不能访问
}
unlock(&rlock)

lock(&rlock);
reader_count--;
if(reader_count == 0)//一开始一批读端的最后一个
{
    unlock(&wlock); //此时读端可以竞争
}
unlock(&rlock);

写端:
lock(&wlock);
// ... 写入
unlock(&wlock);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/897211.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基准线markLine的值超过坐标轴范围导致markline不显示

解决问题&#xff1a;动态设置yAxis的max值&#xff08;解决基准线不在y轴范围&#xff09; yAxis: [{name: 单位&#xff1a;千,...yAxis,nameTextStyle:{...yAxis.nameTextStyle,padding: [0,26,0,24]},paddingLeft:24,paddingRight:26},{name: 单位&#xff1a;百分比,...yA…

Java开发中知识点整理

正则表达式 测试网址 List<?> List<List<Object>> dataList (List<List<Object>>) httpResponseBody.getData();for (List<Object> data : dataList) {DataSourceEntity dataSource new DataSourceEntity(dataSourceEntity);dataSou…

【二刷hot100】day 4

终于有时间刷刷力扣&#xff0c;求实习中。。。。 目录 1.最大子数组和 2.合并区间 3.轮转数组 4.除自身以外数组的乘积 1.最大子数组和 class Solution {public int maxSubArray(int[] nums) {//就是说可以转换为计算左边的最大值&#xff0c;加上中间的值&#xff0c…

Git的原理和使用(六)

本文主要讲解企业级开发模型 1. 引入 交付软件的流程&#xff1a;开发->测试->发布上线 上面三个过程可以详细划分为一下过程&#xff1a;规划、编码、构建、测试、发 布、部署和维护 最初&#xff0c;程序⽐较简单&#xff0c;⼯作量不⼤&#xff0c;程序员⼀个⼈可以完…

一文详解“位运算“在算法中的应用

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; 优选算法专题 目录 位运算的相关介绍&#xff08;重要&#xff09; 136. 只出现一次的数字 191.位1的个数 461. 汉明距离 260. 只出现一…

大模型的经典面试问题及答案,非常详细收藏我这一篇就够了

大语言模型&#xff08;LLM&#xff09;在人工智能中变得越来越重要&#xff0c;在各个行业都有应用。随着对大语言模型专业人才需求的增长&#xff0c;本文提供了一套全面的面试问题和答案&#xff0c;涵盖了基本概念、先进技术和实际应用。如果你正在为面试做准备&#xff0c…

【优选算法篇】在分割中追寻秩序:二分查找的智慧轨迹

文章目录 C 二分查找详解&#xff1a;基础题解与思维分析前言第一章&#xff1a;热身练习1.1 二分查找基本实现解题思路图解分析C代码实现易错点提示代码解读 1.2 在排序数组中查找元素的第一个和最后一个位置解题思路1.2.1 查找左边界算法步骤&#xff1a;图解分析C代码实现 1…

国产大模型基础能力大比拼 - 计数:通义千文 vs 文心一言 vs 智谱 vs 讯飞-正经应用场景的 LLM 逻辑测试

在大语言模型&#xff08;LLM&#xff09;不断涌现的时代&#xff0c;如何评估这些国产大模型的逻辑推理能力&#xff0c;尤其是在处理基础计数问题上的表现&#xff0c;成为了一个备受关注的话题。随着越来越多的国产大模型进入市场&#xff0c;比较它们在不同任务中的表现尤为…

群晖通过 Docker 安装 Gitea

1. 准备工作 1.1 安装 docker 套件 2.2 安装MySQL&#xff08;可选&#xff09; 群晖通过 Docker 安装 MySQL-CSDN博客 如果安装了MySQL&#xff0c;可以创建gitea用户和gitea_db数据库&#xff0c;并且赋予权限 #创建数据库 CREATE DATABASE gitea_db; #确认数据库已创建…

【Jenkins】2024 最新版本的 Jenkins 权限修改为 root 用户启动,解决 permission-denied 报错问题

最新版本的 Jenkins 修改 /etc/sysconfig/jenkins 中的 JENKINS_USERroot不会再生效&#xff0c;需要按照以下配置进行操作&#xff1a; vim /usr/lib/systemd/system/jenkins.service然后重启就可以了 systemctl daemon-reload # 重新加载 systemd 的配置文件 systemctl res…

JavaEE 多线程第三节 (lambda方法实现多线程/Thread属性和方法/前台线程后台线程)

欢迎阅读前序课程JavaEE 多线程第二节 (多线程的简单实现Thread/Runable)-CSDN博客 1. lambda方法实现多线程 public class Test {public static void main(String[] args) throws InterruptedException {Thread t new Thread(()->{while (true){System.out.println("…

探索云边缘与边缘云:技术革新与应用前景

#1024程序员节&#xff5c;征文# 在当今数字化快速发展的时代&#xff0c;云计算已经成为了企业和个人处理和存储数据的重要手段。然而&#xff0c;随着物联网&#xff08;IoT&#xff09;设备的爆炸式增长以及对低延迟、高带宽和实时处理的需求不断增加&#xff0c;云边缘和边…

简单介绍市面上的四款录屏软件!!!!

数字化时代&#xff0c;无论是工作还是生活中&#xff0c;录屏已经成为一个不可或缺的工具。然而&#xff0c;选择一款适合自己需求的录屏软件却并不容易。今日来为大家推荐四款超好用的录屏软件&#xff0c;分析这些软件的优缺点&#xff0c;让你在各种场景下都能轻松捕捉精彩…

vue3 解决背景图与窗口留有间隙的问题

需要实现一个登录界面&#xff0c;login.vue的代码如下&#xff1a; <script> import { ref } from vue;export default {setup() {return {};}, }; </script><template><div id"login-container" class"login-container"><di…

Tcp协议讲解与守护进程

TCP协议&#xff1a;面向链接&#xff0c;面向字节流&#xff0c;可靠通信 创建tcp_server 1.创建套接字 域&#xff1a;依旧选择AF_INET 连接方式&#xff1a; 选择SOCK_STREAM 可靠的 2.bind 3.监听装置 client要通信&#xff0c;要先建立连接&#xff0…

Allegro怎么批量将弧形线改成45度角的线?

Allegro如何将弧形线改成45度角的线? 在用Allegro进行PCB设计时,有时候需要将弧形线改为45度角的线。 具体操作方法如下: 1、选择菜单栏Route 选择Unsupported Prototypes(不支持的原型)→AICC 2、在Options选项卡选择45度 3、在Find选项卡选择Nets,选择网络可以批量转换…

浮动练习(3)

##每台电脑分辨率不同&#xff0c;数值高度宽度需要自己调&#xff0c;仅供参考 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title></title> <style> div{ …

JMeter详细介绍和相关概念

JMeter是一款开源的、强大的、用于进行性能测试和功能测试的Java应用程序。 本篇承接上一篇 JMeter快速入门示例 &#xff0c; 对该篇中出现的相关概念进行详细介绍。 JMeter测试计划 测试计划名称和注释&#xff1a;整个测试脚本保存的名称&#xff0c;以及对该测试计划的注…

《使用Gin框架构建分布式应用》阅读笔记:p108-p126

《用Gin框架构建分布式应用》学习第8天&#xff0c;p108-p126总结&#xff0c;总计18页。 一、技术总结 1.Redis eviction policy (1)什么是 eviction policy? The eviction policy determines what happens when a database reaches its memory limit. (2)配置示例 在r…

Dockerfile样例

一、基础jar镜像制作 ## Dockerfile FROM registry.openanolis.cn/openanolis/anolisos:8.9 RUN mkdir /work ADD jdk17.tar.gz fonts.tar.gz /work/ RUN yum install fontconfig ttmkfdir -y && yum clean all && \chmod -R 755 /work/fonts ADD fonts.conf …