Linux线程补充——周边问题

一、线程池

​ 使用多线程时要注意传参传递堆空间指针变量;

​ 平常定义的缓冲区就是一个简单的数据池;malloc的底层调用了系统调用来申请堆空间是有成本的,如:需要使用页表和MMU将虚拟地址和物理地址建立映射,期间会触发缺页中断;

​ 使用池化技术,直接提前开辟好空间,用户层维护,就不需要交给操作系统提高效率,达到以空间换时间;

​ 线程池就是池化技术的一个分支,提前创建好一批线程和一个任务队列,由主线程push任务,新线程们竞争pop任务,本质上就是一个CP模型;

1.1设计线程池

​ 思路:对线程信息和任务信息创建类,然后用vector组织线程信息和用queue组织任务信息;

​ 任务队列是一个通信场所,多个新线程是消费者,主线程是生产者;

​ 注意:1.线程的执行函数参数和返回值是固定的,不可以写成普通成员函数,因为this指针传递实际上是传递了两个参数,必须是设计成静态成员函数;2.因为静态成员函数的内部要使用成员属性,所以可以使用第四个参数传递this;3.为了满足高并发,处理数据和生产数据不应该加锁;

#pragma once#include <iostream>
#include <vector>
#include <string>
#include <pthread.h>
#include <queue>
#include <unistd.h>struct threadifo
{pthread_t tid;std::string name;
};template <class T>
class threadpool
{static const int defaultnum = 10;private:void lock(){pthread_mutex_lock(&mutex_);}void unlock(){pthread_mutex_unlock(&mutex_);}void wait(){pthread_cond_wait(&cond_, &mutex_);}void signal(){pthread_cond_signal(&cond_);}public:threadpool(int num = defaultnum) : threads_(defaultnum){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}~threadpool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}static void *handlerTask(void *args){threadpool *tp = static_cast<threadpool *>(args);std::string name;for (auto e : tp->threads_){if (e.tid == pthread_self()){name = e.name;}}while (true){tp->lock();while (tp->tasks_.empty()){tp->wait();}T t = tp->pop();tp->unlock();std::cout << name << " ";t();}return nullptr;}void start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name = "Thread-" + std::to_string(i + 1);pthread_create(&(threads_[i].tid), nullptr, handlerTask, (void *)this);}}void push(const T &value){lock();tasks_.push(value);signal();unlock();}T pop(){T t = tasks_.front();tasks_.pop();return t;}private:std::vector<threadifo> threads_;std::queue<T> tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;
};

二、线程的封装

​ 使用包装器设置回调方法,传递任意函数,而不仅限于void*(void*);除了传递函数名,也可以传递函数参数;

#pragma once#include <pthread.h>
#include <iostream>
#include <ctime>
#include <string>
#include <functional>
using callback_t = std::function<void(int, int)>;class thread
{static int num;private:static void *routine(void *args){thread *td = static_cast<thread *>(args);td->func_(td->args1_, td->args2_);return nullptr;}public:thread(callback_t func, int agrs1, int args2) : tid_(0), starttime_(0), isrunning_(false), func_(func), args1_(agrs1), args2_(args2) {}void run(){name_ = "Thread-" + std::to_string(num++);starttime_ = time(nullptr);isrunning_ = true;pthread_create(&tid_, nullptr, routine, this);}void join(){pthread_join(tid_, nullptr);isrunning_ = false;}bool isrunning(){return isrunning_;}uint64_t getstarttime(){return starttime_;}std::string getname(){return name_;}~thread() {}private:pthread_t tid_;std::string name_;uint64_t starttime_; // 启动时间戳bool isrunning_;callback_t func_;int args1_;int args2_;
};
int thread::num = 1;

三、STL,智能指针和线程安全

​ STL中的容器并不是线程安全的;大部分智能指针是线程安全的;

四、线程安全的单例模式

​ 延时加载可以最大程度的提高启动速度;全局变量或者对象在程序加载时就已经创建好了并且生命周期是随进程的;

​ 单例模式就是指整个类最终只能实例化一个对象;

​ 有两种实现模式:1.饿汉模式;2.懒汉模式;

​ 懒汉模式通过延时加载的方式,优化了服务器的启动速度;

将线程池改成懒汉模式:

​ 注意:1.多线程中单例模式是由线程安全问题的,需要对静态变量加锁,并且需要使用的是全局锁,而互斥锁的全局或者静态初始化方式恰好满足这样的场景;2.但是又会引发新的问题,比如实际上只有第一次使用单例才需要进行临界资源的判断,如果加锁就导致每次线程都要串行执行去加锁解锁访问临界资源,但是实际上临界区的代码只会执行一次,之后一直做无效的事情,这样就降低了程序执行的效率;可以再套一层判断,可能会进来一批,但是内部加锁了,最多只能有一个访问,当new成功之后,即使其他线程竞争所成功也立刻判断不满足条件解锁,后面的线程就再也不会进入加锁逻辑,这样既保证了线程安全又可以提高效率;

​ 总结:对于单例模式,1.加锁判断临界资源;2.在外部套一层判断;

template <class T>
class threadpool
{
public:static threadpool<T> *Getinstance(int num = defaultnum){if (nullptr == tp_){pthread_mutex_lock(&lock_);if (tp_ == nullptr){tp_ = new threadpool<T>(num);}pthread_mutex_unlock(&lock_);}return tp_;}
private:threadpool(int num = defaultnum) : threads_(defaultnum){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}~threadpool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}threadpool(const threadpool &args) = delete;threadpool &operator=(const threadpool &args) = delete;
};
template <class T>
threadpool<T> *threadpool<T>::tp_ = nullptr;

五、常见的各种锁

​ 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。如:互斥锁和信号量;

​ 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。 即允许其他线程修改但是当前线程会进行数据的修正

​ CAS(compare and swap)操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。原子的;

5.1自旋锁

​ 自旋锁不挂起而是周而复始不断地申请锁,成功进入临界区,失败返回并且立刻重新申请锁;

​ 实现自旋锁:

while(true){int ret = pthread_mutex_try_lock(&mutex);if(ret == 0)//成功退出循环,失败继续循环{break;}
}

​ 自旋锁接口:

#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);//底层加了循环,申请失败了上层感觉就是阻塞
int pthread_spin_unlock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);//和互斥锁的try_lock是一样的
int pthread_spin_destroy(pthread_spinlock_t *lock);
int  pthread_spin_init(pthread_spinlock_t *lock, intpshared);
//第二个参数是是否共享,默认为0;

​ 选择挂起等待锁还是自旋锁完全取决于临界区执行时间的长短;

​ 当考虑线程在临界区的时间时,如果执行时间短,则其他进程选择自选方式,如果执行时间长,其它线程选择挂起等待;因为挂起等待也是有时间成本的,执行时间短却选择了挂起等待方式,就会导致频繁地进行挂起和唤醒操作,而自选方式频繁地竞争,但是时间短可能比起挂起等待耗费的时间还要少,效率更高;执行时间长选择挂起等待则较为合适;

六、读者写者问题

6.1读写锁

​ 在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。

​ 使用读写锁解决问题;分析是要注意三种关系,两种角色,一个通信场所

​ 读者之间:共享;读者不会修改数据;

​ 写者之间:互斥加竞争;

​ 读写者之间:互斥加同步;

​ 与CP模型相比实现时不要对读者之间进行同步与互斥;

​ 使用读写锁其实就可以实现读者写者问题,接口如下:

​ 对于读写锁,锁处于无锁时,读写锁请求都可以申请;锁处于读锁时,读锁可以申请,写锁一定失败;锁处于写锁时,读写锁都不可以申请;

//默认读者优先可以修改
#include <pthread.h>
int  pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int  pthread_rwlock_init(pthread_rwlock_t  *restrict rwlock, const pthread_rwlockattr_t *restrict attr);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

6.2模型理解

​ 一般时读者多写者少,表现出来就是读者竞争能力强,写者会存在饥饿问题;但是此现象在读者写者中是正常的;所以可以设计同步策略如:读者优先(读者读完了,写者才进入)、写者优先(当写者到了,先写者执行);

​ 用互斥锁模拟读写锁,读者之间可以看到读者计数器和两个互斥锁分别用于读写互斥,读者由于修改读者计数器所以要加锁访问,当计数器为1时,读者申请写者锁;当前读者线程申请写者互斥锁成功,如果写者在临界区,调度回来时就会阻塞,不在就无法访问临界区也阻塞;此时就可以读者读取共享,结束后判断计数器共享资源需要加锁,当最后一个读者线程时,再将写者锁释放,这样写者才能执行,实现读者优先;

//读者优先
int rcount=0;
mutex_t rlock,wlock;
lock(&rlock);
rcount++;
if(rcount==1)lock(&wlock);
unlock(&rlock);
//共享读取
lock(&rlock);
rcount--;
if(rcount==1)unlock(&wlock);
unnlock(&rlock);
//写者
lock(&wlock);
//写入
unlock(&wlock);

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯刷题记录之数字王国之军训排队

记录 卡了半天&#xff0c;check函数中的temp % ele 0写成了ele % temp 0就挺无语的 思路 这个晚上在补 代码 import java.util.*; public class Main{static List<List<Integer>> que new ArrayList<>();static int MIN Integer.MAX_VALUE;static i…

Personal Website

Personal Website Static Site Generators hexo hugo jekyll Documentation Site Generator gitbook vuepress vitepress docsify docute docusaurus Deployment 1. GitHub Pages 2. GitLab Pages 3. vercel 4. netlify Domain 域名注册 freessl 域名解析域名…

Java基础面试整理

1. Java的跨平台优势&#xff1f; 写好的Java源文件通过Javac命令编译生成class文件(中间文件)&#xff0c;然后JVM对class文件进行执行生成机器语言然后机器语言在平台中操作&#xff0c;Java在不同的平台下都有对应的不同版本的JVM&#xff0c;JVM可以识别字节码文件从而运行…

DMA控制器

前言 大家好&#xff0c;我是jiantaoyab&#xff0c;这是我作为学习笔记的25篇&#xff0c;本篇文章给大家介绍DMA。 无论 I/O 速度如何提升&#xff0c;比起 CPU&#xff0c;总还是太慢。如果我们对于 I/O 的操作&#xff0c;都是由 CPU 发出对应的指令&#xff0c;然后等待…

带气压高度的三点法MATLAB定位函数(基于加权最小二乘法WLS)

函数作用 输入气压高度值、待定位节点与各个锚节点的距离、锚节点位置、权重&#xff08;可选&#xff09;&#xff0c;输出待测点位置 程序源码 function [p_out] triposition_weight_Ver2(dairheight,R_calcu,baseP,varargin) % airghight 由气压高度计算的锚节点-待测节…

【数据结构】线性表的定义与基本操作

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;数据结构 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

用户态和内核态:操作系统权限与运行模式解析

在现代计算机操作系统中&#xff0c;用户态&#xff08;User Mode&#xff09;和内核态&#xff08;Kernel Mode&#xff09;是两种重要的运行模式&#xff0c;用于区分用户程序与操作系统核心之间的权限和特权级别。深入理解这两种模式对于理解操作系统的工作原理至关重要。 …

关于vector的size,unsigned int类型

平时写代码很少注意溢出&#xff0c;这次被遇上了。 题目&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool containsNearbyDuplicate(vector<int>& nums, int k) {//超时,O(n2)// for(int i0;i<nums.size();i)// {// fo…

学习次模函数-第2章 定义

纵观本专著&#xff0c;我们认为及其幂集&#xff08;即&#xff0c; 所有子集的集合&#xff09;&#xff0c;其基数为。我们也考虑一个实值集函数&#xff0c;使得。 与凸函数的一般约定相反&#xff08;见附录A&#xff09;&#xff0c;我们不允许函数有无穷大的值。 次模分…

ssm004新生报到系统+jsp

新生报到系统的设计与实现 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对新生报到信息管理混乱&#xff0c;出错率…

虚拟线圈法的车辆统计_3.12

目标 车流量统计的方法实现车流量检测 基于虚拟线圈法的车辆统计是一种利用计算机视觉技术模拟传统物理线圈检测原理&#xff0c;对交通视频流中的车辆进行计数的方法。在传统交通监控系统中&#xff0c;物理线圈是通过感应车辆经过时产生的电磁场变化来记录车辆流量。这种方式…

基于深度学习的图像分类方法

基于深度学习的图像分类方法已经成为计算机视觉领域的重要组成部分。这类方法通常涉及使用深度神经网络&#xff0c;特别是卷积神经网络&#xff08;CNNs&#xff09;来识别和分类图像中的对象。以下是基于深度学习的图像分类方法的一些关键要点&#xff1a; 卷积神经网络&…

数据库ER图和ER图转换关系模式

ER图&#xff08;Entity-Relationship Diagram&#xff09;是一种用于描述实体&#xff08;Entity&#xff09;之间关系的图形化工具。ER图是由实体、属性和关系组成的。 实体&#xff08;Entity&#xff09;指的是现实世界中的一个对象或概念&#xff0c;如人、物、事件等。每…

安全问题

信息安全具有不可证明的特性&#xff0c;只能说在某些已知攻击下是安全的&#xff0c;对于将来新的攻击是否安全仍然很难断言。 信息系统不安全的主要因素有程序设计漏洞、用户操作不当和外部攻击。外部攻击形式主要有计算机病毒、恶意软件、黑客攻击等。目前计算机系统在理论…

模型怎么处理不同尺寸的输入图像

1.有全连接层的的CNN模型 卷积能够处理不同尺寸的输入图像&#xff0c;但全连接层不行&#xff0c;因此在送入全连接层之前需将卷积层提取的特征转换为一个固定长度的特征向量。 那么如何转换&#xff1f; 1.1 GAP(Global Average Pooling)全局平均池化 直接代码举例&#…

MySQL数据库备份及恢复

一、数据库备份的分类 1.1 从物理与逻辑的角度 从物理与逻辑的角度&#xff0c;备份可分为物理备份、逻辑备份 物理备份:对数据库操作系统的物理文件(如数据文件日志文件等)的备份 物理备份方法 冷备份(脱机备份)是在关闭数据库的时候进行的 热备份(联机备份):数…

大数据基础:Linux基础详解

课程介绍 本课程主要通过对linux基础课程的详细讲解&#xff0c;让大家熟练虚拟机的安装使用&#xff0c;Linux系统的安装配置&#xff0c;学习掌握linux系统常用命令的使用&#xff0c;常用的软件安装方法&#xff0c;制作快照&#xff0c;克隆&#xff0c;完成免密登录&…

校园跑腿大学生创业平台

校园跑腿大学生创业是一个充满挑战与机遇的领域。随着大学生消费能力的提升和校园生活的多样化&#xff0c;校园跑腿服务的需求日益旺盛&#xff0c;这为有志于创业的大学生提供了良好的市场机遇。 首先&#xff0c;创业成本低是校园跑腿大学生创业的一大优势。校园跑腿服务通常…

异常处理篇

文章目录 1. try 和 except2. 多个 except 块3. else 块4. finally 块5. raise 语句6. 自定义异常7. 异常链 1. try 和 except try 块包含可能引发异常的代码&#xff0c;而 except 块则包含处理这些异常的代码。 try:# 尝试执行可能会引发异常的代码result 10 / 0 # 这将引…

【Unity】uDD插件抓屏文字显示不清晰怎么办?

【背景】 之前介绍过用一款简称uDD&#xff08;uDesktopDuplication&#xff09;的开源插件抓取电脑桌面。整体效果不错&#xff0c;看电影很流畅。但是当切换到文档&#xff0c;或者仔细看任何UI的文字部分时&#xff0c;发现就模糊了。 【分析】 由于是依托于Canvas上的Te…