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

Linux之线程池

线程池

  • 线程池概念
  • 线程池的应用场景
  • 线程池实现原理
  • 单例模式下线程池实现
  • STL、智能指针和线程安全
  • 其他常见的各种锁

线程池概念

线程池:一种线程使用模式。

线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。

这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

线程池的应用场景

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了;
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求;
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

线程池实现原理

线程池通过一个线程安全的阻塞任务队列加上一个或一个以上的线程实现,线程池中的线程可以从阻塞队列中获取任务进行任务处理,当线程都处于繁忙状态时可以将任务加入阻塞队列中,等到其它的线程空闲后进行处理。
在这里插入图片描述

testMain.cc

主线程任务逻辑启动线程,不断向任务队列中push任务就可以了,此时线程接收到任务就会进行处理:

#include <iostream>
#include <ctime>
#include <unistd.h>
#include "threadPool.hpp"
#include "Task.hpp"
#include "log.hpp"int main()
{srand((unsigned int)time(nullptr) ^ getpid());ThreadPool<Task>* tp = new ThreadPool<Task>();//启动线程tp->run();//主线程执行任务while(true){int x = rand() % 100 + 1;usleep(1000);int y = rand() % 50 + 1;Task t(x, y, [](int x, int y)->int{return x + y;});logMessage(DEBUG, "制作任务完成:%d+%d=?", x, y);// std::cout << "制作任务完成: " << x << "+" << y << "=?" << std::endl;//将任务推送到线程池中tp->pushTask(t);sleep(1);}return 0;
}

thread.hpp

我们对创建线程进行封装,包含线程名,线程个数,回调函数,线程ID等;

#pragma once#include <iostream>
#include <string>
#include <functional>
#include <cstdio>typedef void *(*func_t)(void *);class ThreadData
{
public:std::string name_;void *args_;
};class Thread
{
public:Thread(int num, func_t callback, void *args) : func_(callback){char nameBuffer[64];snprintf(nameBuffer, sizeof nameBuffer, "Thread-%d", num);name_ = nameBuffer;tdata_.args_ = args;tdata_.name_ = name_;}void start(){pthread_create(&tid_, nullptr, func_, (void *)&tdata_);}void join(){pthread_join(tid_, nullptr);}std::string name(){return name_;}~Thread(){}private:std::string name_; // 线程名int num_;          // 线程个数func_t func_;      // 回调函数pthread_t tid_;    // 线程IDThreadData tdata_;
};

threadPool.hpp

线程池中我们需要用注意的是:

  1. 需要用到条件变量与互斥锁,因为线程池中的任务队列会被多个执行流访问,所以我们必须引入互斥锁;
  2. 当线程池中任务队列为满时,我们此时push任务就无法push进去,此时就需要挂起等待,直到线程将某一任务执行完毕,唤醒等待队列,才可以继续进行push,我们执行任务也是一样,只有当任务队列中有任务时,我们才可以执行,否则就需要挂起等待,直到有任务生成才去获取任务;
  3. 线程执行例程需要设置为静态方法,原因如下:
  • 使用pthread_create函数创建线程时,需要为创建的线程传入一个routine(执行例程),该routine只有一个参数类型为void的参数,以及返回类型为void的返回值。因为我们将线程池封装为一个类,此时routine函数就包含两个参数,第一个参数就是隐含的this指针,直接用来创建线程程序是会报错的;
  • 静态成员函数属于类,而不属于某个对象,也就是说静态成员函数是没有隐藏的this指针的,因此我们需要将routine设置为静态方法,此时routine函数才真正只有一个参数类型为void*的参数。
  • 但是在静态成员函数内部无法调用非静态成员函数,而我们需要在routine函数当中调用该类的某些非静态成员函数,比如pop。因此我们需要在创建线程时,向routine函数传入的当前对象的this指针,此时我们就能够通过该this指针在routine函数内部调用非静态成员函数了。
#pragma once#include <iostream>
#include <vector>
#include <queue>
#include <unistd.h>
#include "thread.hpp"
#include "lockGuard.hpp"
#include "log.hpp"#define NUM 3template <class T>
class ThreadPool
{
public:pthread_mutex_t *getMutex(){return &lock;}bool isEmpty(){return task_queue_.empty();}void waitCond(){pthread_cond_wait(&cond, &lock);}T getTask(){T t = task_queue_.front();task_queue_.pop();return t;}public:ThreadPool(int thread_num = NUM) : num_(thread_num){pthread_mutex_init(&lock, nullptr);pthread_cond_init(&cond, nullptr);for (int i = 1; i <= num_; i++){threads_.push_back(new Thread(i, routine, this));}}// 生产void run(){for (auto &iter : threads_){iter->start();// std::cout << iter->name() << "启动成功" << std::endl;logMessage(NORMAL, "%s %s", iter->name().c_str(), "启动成功");}}static void *routine(void *args){ThreadData *td = (ThreadData *)args;ThreadPool<T> *tp = (ThreadPool<T> *)td->args_;while (true){T task;{LockGuard lockguard(tp->getMutex());while (tp->isEmpty())tp->waitCond();task = tp->getTask();}// 处理任务task(td->name_);}}void pushTask(const T &task){LockGuard lockguard(&lock);task_queue_.push(task);pthread_cond_signal(&cond);}~ThreadPool(){for (auto &iter : threads_){iter->join();delete iter;}pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);}private:std::vector<Thread *> threads_; // 线程组int num_;std::queue<T> task_queue_; // 任务队列pthread_mutex_t lock; // 互斥锁pthread_cond_t cond;  // 条件变量
};

lockGuard.hpp

为了代码更加的模块化,我们将互斥锁进行一个封装成一个RAII风格的锁,创建对象是调用构造函数加锁,出作用域调用析构函数解锁:

#pragma once#include <iostream>
#include <pthread.h>class Mutex
{
public:Mutex(pthread_mutex_t *mtx) : pmtx_(mtx){}void lock(){pthread_mutex_lock(pmtx_);}void unlock(){pthread_mutex_unlock(pmtx_);}~Mutex(){}private:pthread_mutex_t *pmtx_;
};class LockGuard
{
public:LockGuard(pthread_mutex_t* mtx) : mtx_(mtx){mtx_.lock();}~LockGuard(){mtx_.unlock();}private:Mutex mtx_;
};

Task.hpp

这是一个加法的计算任务:

#pragma once#include <iostream>
#include <string>
#include <functional>typedef std::function<int(int, int)> tfunc_t;class Task
{
public:Task(){}Task(int x, int y, tfunc_t func) : x_(x), y_(y), func_(func){}void operator()(const std::string& name){// std::cout << "线程 " << name << " 处理完成, 结果是: " << x_ << "+" << y_ << "=" << func_(x_, y_) << std::endl;logMessage(WARNING, "%s处理完成:%d+%d = %d | %s | %d", name.c_str(), x_, y_, func_(x_, y_), __FILE__, __LINE__);}private:int x_;int y_;tfunc_t func_;
};

log.hpp

此处我们在设置一个日志文件,完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名);

#pragma once#include <iostream>
#include <string>
#include <functional>typedef std::function<int(int, int)> tfunc_t;class Task
{
public:Task(){}Task(int x, int y, tfunc_t func) : x_(x), y_(y), func_(func){}void operator()(const std::string& name){// std::cout << "线程 " << name << " 处理完成, 结果是: " << x_ << "+" << y_ << "=" << func_(x_, y_) << std::endl;logMessage(WARNING, "%s处理完成:%d+%d = %d | %s | %d", name.c_str(), x_, y_, func_(x_, y_), __FILE__, __LINE__);}private:int x_;int y_;tfunc_t func_;
};

运行代码后,我们就会发现此时就有4个线程,其中1个为主线程:
在这里插入图片描述
并且我们会发现这3个线程在处理时会呈现出一定的顺序性,因为主线程是每秒push一个任务,这3个线程只会有一个线程获取到该任务,其他线程都会在等待队列中进行等待,当该线程处理完任务后就会因为任务队列为空而排到等待队列的最后,当主线程再次push一个任务后会唤醒等待队列首部的一个线程,这个线程处理完任务后又会排到等待队列的最后,因此这3个线程在处理任务时会呈现出一定的顺序性。
在这里插入图片描述

单例模式下线程池实现

单例模式:指的就是一个类只能创建一个对象,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

接下来我们以懒汉模式为例,来实现我们的线程池:

  1. 首先,我们需要将线程池中构造函数设置为私有,因为我们不想让他被多次访问,同时我们也要防止赋值和拷贝的情况发生,我们需要将拷贝构造函数与赋值运算符重载函数设置为私有或者删除;
  2. 提供一个指向单例对象的static指针,并在程序入口之前先将其初始化为空;
  3. 提供一个全局访问点获取单例对象。

通过上述三点就可以将我们的代码做出如下改变:

threadPool.hpp

#pragma once#include <iostream>
#include <vector>
#include <queue>
#include <unistd.h>
#include "thread.hpp"
#include "lockGuard.hpp"
#include "log.hpp"#define NUM 3template <class T>
class ThreadPool
{
public:pthread_mutex_t *getMutex(){return &lock;}bool isEmpty(){return task_queue_.empty();}void waitCond(){pthread_cond_wait(&cond, &lock);}T getTask(){T t = task_queue_.front();task_queue_.pop();return t;}private:ThreadPool(int thread_num = NUM) : num_(thread_num){pthread_mutex_init(&lock, nullptr);pthread_cond_init(&cond, nullptr);for (int i = 1; i <= num_; i++){threads_.push_back(new Thread(i, routine, this));}}ThreadPool(const ThreadPool<T> &other) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> &other) = delete;public:static ThreadPool<T> *getThreadPool(int num = NUM){if (thread_ptr == nullptr){LockGuard lockguard(&mutex);if (thread_ptr == nullptr){thread_ptr = new ThreadPool<T>(num);}}return thread_ptr;}// 生产void run(){for (auto &iter : threads_){iter->start();// std::cout << iter->name() << "启动成功" << std::endl;logMessage(NORMAL, "%s %s", iter->name().c_str(), "启动成功");}}static void *routine(void *args){ThreadData *td = (ThreadData *)args;ThreadPool<T> *tp = (ThreadPool<T> *)td->args_;while (true){T task;{LockGuard lockguard(tp->getMutex());while (tp->isEmpty())tp->waitCond();task = tp->getTask();}// 处理任务task(td->name_);}}void pushTask(const T &task){LockGuard lockguard(&lock);task_queue_.push(task);pthread_cond_signal(&cond);}~ThreadPool(){for (auto &iter : threads_){iter->join();delete iter;}pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);}private:std::vector<Thread *> threads_; // 线程组int num_;std::queue<T> task_queue_; // 任务队列pthread_mutex_t lock; // 互斥锁pthread_cond_t cond;  // 条件变量static ThreadPool<T> *thread_ptr;static pthread_mutex_t mutex;
};template <typename T>
ThreadPool<T> *ThreadPool<T>::thread_ptr = nullptr;template <typename T>
pthread_mutex_t ThreadPool<T>::mutex = PTHREAD_MUTEX_INITIALIZER;

我们需要注意的是getThreadPool函数在创建对象过程中需要双检查加锁,因为简单的在if语句前后进行加锁解锁操作的话,后续在获取创建的单例对象操作时就会进行大量无意义的加锁解锁操作,我们进行双检查操作以后,就会加锁之前在进行一次判断,不为空就直接返回,就避免了后序无意义的加锁解锁操作;

testMain.cc

#include <iostream>
#include <ctime>
#include <unistd.h>
#include "threadPool.hpp"
#include "Task.hpp"
#include "log.hpp"int main()
{srand((unsigned int)time(nullptr) ^ getpid());// ThreadPool<Task>* tp = new ThreadPool<Task>();//启动线程ThreadPool<Task>::getThreadPool()->run();//主线程执行任务while(true){int x = rand() % 100 + 1;usleep(1000);int y = rand() % 50 + 1;Task t(x, y, [](int x, int y)->int{return x + y;});logMessage(DEBUG, "制作任务完成:%d+%d=?", x, y);// std::cout << "制作任务完成: " << x << "+" << y << "=?" << std::endl;//将任务推送到线程池中ThreadPool<Task>::getThreadPool()->pushTask(t);sleep(1);}return 0;
}

STL、智能指针和线程安全

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

不是。原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响,而且对于不同的容器,加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶),因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全。

智能指针是否是线程安全的?

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

其他常见的各种锁

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

相关文章:

Linux之线程池

线程池 线程池概念线程池的应用场景线程池实现原理单例模式下线程池实现STL、智能指针和线程安全其他常见的各种锁 线程池概念 线程池&#xff1a;一种线程使用模式。 线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能。而线程池维护着多个线程&#xff0c;等待…...

MAC安装stable diffusion

./webui.sh --precision full --no-half-vae --disable-nan-check --api Command: "/Users/xxxx/aigc/stable-diffusion-webui/venv/bin/python3" -m pip install torch2.0.1 torchvision0.15.2 Error code: 2 执行命令&#xff1a; pip install torch2.0.1 torchvi…...

FPGA_状态机工作原理

FPGA_状态机介绍和工作原理 状态机工作原理Mealy 状态机模型Moore 状态机模型状态机描述方式代码格式 总结 状态机工作原理 状态机全称是有限状态机&#xff08;Finite State Machine、FSM&#xff09;&#xff0c;是表示有限个状态以及在这些状态之间的转移和动作等行为的数学…...

【python练习】python斐波那契数列超时问题

计算斐波那契数列第n项的数字 Description计算斐波那契数列第n项的数字&#xff0c;其中f(1)f(2)1,f(n)f(n-1)f(n-2)&#xff0c;如1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5,......Input 正整数n(n<100)Output 一个整数f(n)Sample Input 1 8 Sample Output 1…...

SpringCloud 微服务全栈体系(五)

第七章 Feign 远程调用 先来看我们以前利用 RestTemplate 发起远程调用的代码&#xff1a; 存在下面的问题&#xff1a; 代码可读性差&#xff0c;编程体验不统一 参数复杂 URL 难以维护 Feign 是一个声明式的 http 客户端&#xff0c;官方地址&#xff1a;https://github.…...

msvcp140.dll丢失的正确解决方法

在使用电脑中我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcp140.dll丢失”。这个错误通常会导致某些应用程序无法正常运行。为了解决这个问题&#xff0c;我们需要采取一些措施来修复丢失的msvcp140.dll文件。本文将介绍6个不同的解决方法&#xff0c;帮助读者解决…...

go pprof 如何使用 --chatGPT

gpt: pprof 是 Go 语言的性能分析工具&#xff0c;它可以用来检测 CPU 使用情况、内存使用情况、以及阻塞情况。你可以使用 pprof 来帮助诊断程序的性能问题&#xff0c;包括内存泄漏。 以下是如何使用 pprof 来分析内存泄漏的基本步骤&#xff1a; 1. **导入 pprof 包**&am…...

大数据可视化BI分析工具Apache Superset实现公网远程访问

大数据可视化BI分析工具Apache Superset实现公网远程访问 文章目录 大数据可视化BI分析工具Apache Superset实现公网远程访问前言1. 使用Docker部署Apache Superset1.1 第一步安装docker 、docker compose1.2 克隆superset代码到本地并使用docker compose启动 2. 安装cpolar内网…...

软考系统架构师知识点集锦二:软件工程

一、考情分析 二、考点精讲 2.1 软件过程模型 &#xff08;1&#xff09;原型模型 典型的原型开发方法模型。适用于需求不明确的场景,可以帮助用户明确需求。可以分为[抛弃型原型]与[演化型原型] 原型模型两个阶段: 1、原型开发阶段;2、目标软件开发阶段。 &#x…...

Go并发:使用sync.Pool来性能优化

简介 在Go提供如何实现对象的缓存池功能&#xff1f;常用一种实现方式是&#xff1a;sync.Pool, 其旨在缓存已分配但未使用的项目以供以后重用&#xff0c;从而减轻垃圾收集器&#xff08;GC&#xff09;的压力。 快速使用 sync.Pool的结构也比较简单&#xff0c;常用的方法…...

git stash的使用方法

git stash的使用方法 应用场景 当我们在开发一个新功能的时候&#xff0c;或者开发到一半&#xff0c;然后就收到了线上master 出现了bug&#xff0c;当分支开发已经进行了或者进行到一半了&#xff0c;这时怎么办呢&#xff1f; 这时解决方案有两种&#xff1a;一种是先先将当…...

【影刀演示_发送邮件的格式化HTML留存】

发送邮件的格式化HTML留存 纯文本&#xff1a; 亲爱的小张: 端午节将至&#xff0c;公司为了感谢大家一年以来的辛勤工作和付出&#xff0c;特别为大家准备了京客隆超市福利卡&#xff0c;希望为大家带来些许便利和节日的喜悦。 以下是您的福利卡卡号和密码&#xff0c;请您…...

深度学习(4)---生成式对抗网络(GAN)

文章目录 一、原理讲述1.1 概念讲解1.2 生成模型和判别模型 二、训练过程2.1 训练原理2.2 损失函数 三、应用 一、原理讲述 1.1 概念讲解 1. 生成式对抗网络&#xff08;Generative Adversarial Network&#xff0c;GAN&#xff09;是一种深度学习模型&#xff0c;是近年来复杂…...

ThinkPad电脑HDMI接口失灵如何解决?

ThinkPad电脑HDMI接口失灵如何解决&#xff1f; 如果平时正常使用的外接显示器&#xff0c;某天突然无法使用了&#xff0c;重新插拔依然无信号的话&#xff0c;可以打开系统的设备管理器&#xff08;快捷键winx&#xff09;&#xff0c;首先看一下监视器的识别情况&#xff0c…...

第四部分:JavaScript

一&#xff1a;jQuery 1.1&#xff1a;jQuery介绍 什么是jQuery&#xff1f; jQuery是JavaScript和查询&#xff08;Query&#xff09;&#xff0c;它是辅助JavaScript开发的js类库 jQuery的核心思想 核心思想是write less&#xff0c;do more&#xff0c;所以它实现了很多浏览…...

【游戏开发】【心法】游戏设计心法系列1-以玩法为核心去设计游戏

游戏的本质 游戏的魔法在于寻找隐藏事物之间的联系。 游戏的魅力在于随着玩家逐渐发现并了解游戏世界的方方面面&#xff0c;他会得到一种丰富而深厚的体验。 挑战&#xff0c;竞争和互动是游戏玩法的三大要素。 规则&#xff0c;过程&#xff0c;目标则是游戏内容的要素。 如…...

chrome谷歌浏览器取消网页所有剪切板的授权方法步骤

地址栏输入 chrome://settings/content/clipboard选择 不允许网站查看您剪贴板中的文字或图片 ———————————————— 版权声明&#xff1a;本文为CSDN博主「一切V随缘」的原创文章&#xff0c;遵循CC 4.0 BY-SA版权协议&#xff0c;转载请附上原文出处链接及本声明…...

目标检测算法改进系列之嵌入Deformable ConvNets v2 (DCNv2)

Deformable ConvNets v2 简介&#xff1a;由于构造卷积神经网络所用的模块中几何结构是固定的&#xff0c;其几何变换建模的能力本质上是有限的。在DCN v1中引入了两种新的模块来提高卷积神经网络对变换的建模能力&#xff0c;即可变形卷积 (deformable convolution) 和可变形…...

最新发布!阿里云卓越架构框架重磅升级

云布道师 10 月 19 日阿里云峰会山东上&#xff0c;阿里云重磅升级《阿里云卓越架构白皮书》&#xff0c;助力企业在阿里云上构建更加安全、高效、稳定的云架构。《阿里云卓越架构白皮书》在今年的阿里云峰会粤港澳大湾区首度亮相&#xff0c;这是阿里云基于多年服务各行各业客…...

如何监听/抓取两个设备/芯片之间“UART串口”通信数据--监视TXD和RXD

案例背景&#xff1a;全网仅此一篇&#xff01;&#xff01;&#xff01; 两个设备/芯片之间采用UART串口通信。我们如何实现芯片1 TXD – > 芯片2 RXD&#xff0c;芯片2 TXD --> 芯片1 RXD两个单线链路上的数据抓取和监听&#xff1f;这篇博客将告诉您。 目录 1 什么是…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

云原生玩法三问:构建自定义开发环境

云原生玩法三问&#xff1a;构建自定义开发环境 引言 临时运维一个古董项目&#xff0c;无文档&#xff0c;无环境&#xff0c;无交接人&#xff0c;俗称三无。 运行设备的环境老&#xff0c;本地环境版本高&#xff0c;ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...