Linux线程池(下)(34)

文章目录

  • 前言
  • 一、v3版本
  • 二、单例模式
    • 概念
    • 特点
    • 简单实现
  • 三、其余问题
    • STL线程安全问题
    • 智能指针线程安全问题
    • 其他锁的概念
  • 总结


前言

  加油!!!


一、v3版本

  「优化版」:从任务队列入手,引入 「生产者消费者模型」,同时引入 RAII 风格的锁,实现自动化加锁与解锁

  当前的 线程池 设计已经完成的差不多了,接下来重点在于完善其他地方,比如 任务队列及锁的优化

  线程池 专注于 任务处理,至于如何确保任务装载及获取时的线程安全问题,交给 「生产者消费者模型」(基于阻塞队列) 就行了,线程池v3版本的代码可以优化成下面这个样子

#pragma once#include <vector>
#include <string>
#include <memory>
#include <functional>
#include <unistd.h>
#include <pthread.h>#include "Task.hpp"
#include "Thread.hpp"
#include "BlockingQueue.hpp" // CP模型#define THREAD_NUM 10template<class T>
class ThreadPool
{using func_t = std::function<void(T&)>; // 包装器public:ThreadPool(func_t func, int num = THREAD_NUM):_num(num), _func(func){}~ThreadPool(){// 等待线程退出for(auto &t : _threads)t.join();}void init(){// 创建一批线程for(int i = 0; i < _num; i++)_threads.push_back(Thread(i, threadRoutine, this));}void start(){// 启动线程for(auto &t : _threads)t.run();}// 提供给线程的回调函数(已修改返回类型为 void)static void threadRoutine(void *args){// 避免等待线程,直接剥离pthread_detach(pthread_self());auto ptr = static_cast<ThreadPool<T>*>(args);while (true){// 从CP模型中获取任务T task = ptr->popTask();task();ptr->callBack(task); // 回调函数}}// 装载任务void pushTask(const T& task){_blockqueue.Push(task);}protected:func_t callBack(T &task){_func(task);}T popTask(){T task;_blockqueue.Pop(&task);return task;}private:std::vector<Thread> _threads;int _num; // 线程数量BlockQueue<T> _blockqueue; // 阻塞队列func_t _func;
};

  之前的 互斥锁、条件变量 相关操作交给 「生产者消费者模型」 处理,线程池 不必关心,关于 「生产者消费者模型」 的实现,大家可自行参考我之前写的文章 《生产者与消费者模型》

  手动 加锁、解锁 显得不够专业,并且容易出问题,比如忘记释放锁资源而造成死锁,因此我们可以设计一个小组件 LockGuard,实现 RAII 风格的锁:初始化创建,析构时销毁

  将这个小组件加入 BlockingQueue.hpp 中,可以得到以下代码

#pragma once#include <queue>
#include <mutex>
#include <pthread.h>
#include "LockGuard.hpp"// 命名空间,避免冲突
#define DEF_SIZE 10template<class T>
class BlockQueue
{
public:BlockQueue(size_t cap = DEF_SIZE):_cap(cap){// 初始化锁与条件变量pthread_mutex_init(&_mtx, nullptr);pthread_cond_init(&_pro_cond, nullptr);pthread_cond_init(&_con_cond, nullptr);}~BlockQueue(){// 销毁锁与条件变量pthread_mutex_destroy(&_mtx);pthread_cond_destroy(&_pro_cond);pthread_cond_destroy(&_con_cond);}// 生产数据(入队)void Push(const T& inData){// 加锁(RAII风格)LockGuard lock(&_mtx);// 循环判断条件是否满足while(IsFull()){pthread_cond_wait(&_pro_cond, &_mtx);}_queue.push(inData);// 可以加策略唤醒,比如生产一半才唤醒消费者pthread_cond_signal(&_con_cond);// 自动解锁}// 消费数据(出队)void Pop(T* outData){// 加锁(RAII 风格)LockGuard lock(&_mtx);// 循环判读条件是否满足while(IsEmpty()){pthread_cond_wait(&_con_cond, &_mtx);}*outData = _queue.front();_queue.pop();// 可以加策略唤醒,比如消费完后才唤醒生产者pthread_cond_signal(&_pro_cond);// 自动解锁}private:// 判断是否为满bool IsFull(){return _queue.size() == _cap;}// 判断是否为空bool IsEmpty(){return _queue.empty();}private:std::queue<T> _queue;size_t _cap; // 阻塞队列的容量pthread_mutex_t _mtx; // 互斥锁pthread_cond_t _pro_cond; // 生产者条件变量pthread_cond_t _con_cond; // 消费者条件变量
};

  最后引入 main.cc,并编译运行程序,查看结果是否正确

#include "ThreadPool.hpp"
#include <memory>typedef Task<int> type;// 回调函数
void callBack(type& task)
{// 获取计算结果后打印std::string ret = task.getResult();std::cout << "计算结果为: " << ret << std::endl;
}int main()
{std::unique_ptr<ThreadPool<type>> ptr(new ThreadPool<type>(callBack));ptr->init();ptr->start();// 还有后续动作while(true){// 输入 操作数 操作数 操作符int x = 0, y = 0;char op = '+';std::cout << "输入 x: ";std::cin >> x;std::cout << "输入 y: ";std::cin >> y;std::cout << "输入 op: ";std::cin >> op;// 构建任务对象type task(x, y, op);// 装载任务ptr->pushTask(task);}return 0;
}

二、单例模式

概念

  代码构建类,类实例化出对象,这个实例化出的对象也可以称为 实例,比如常见的 STL 容器,在使用时,都是先根据库中的类,形成一个 实例 以供使用;正常情况下,一个类可以实例化出很多很多个对象,但对于某些场景来说,是不适合创建出多个对象的

  比如本文中提到的 线程池,当程序运行后,仅需一个 线程池对象 来进行高效任务计算,因为多个 线程池对象 无疑会大大增加调度成本,因此需要对 线程池类 进行特殊设计,使其只能创建一个 对象,换句话说就是不能让别人再创建对象

  在一个程序中只允许实例化出一个对象,可以通过 单例模式 来实现,单例模式 是非常 经典、常用、常考 的设计模式

特点

  单例模式 最大的特点就是 只允许存在一个对象(实例),这就好比现在的 一夫一妻制 一样,要是在古代,单例模式 肯定不被推崇

在这里插入图片描述
  在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百 GB) 到内存中,此时往往要用一个 单例 的类来管理这些数据;在我们今天的场景中,也需要一个 单例线程池 来协同生产者与消费者

简单实现

  单例模式 有两种实现方向:饿汉 与 懒汉,它们避免类被再次创建出对象的手段是一样的:构造函数私有化、删除拷贝构造

  只要外部无法访问 构造函数,那么也就无法构建对象了,比如下面这个类 Signal

#pragma once
33333333333333
#include <iostream>class Signal
{
private:// 构造函数私有化Signal(){}// 删除拷贝构造Signal(const Signal&) = delete;
};

  当外界试图创建对象时
在这里插入图片描述
  当然这只实现了一半,还有另一半是 创建一个单例对象,既然外部受权限约束无法创建对象,那么类内是肯定可以创建对象的,只需要创建一个指向该类对象的 静态指针 或者一个 静态对象,再初始化就好了;因为外部无法访问该指针,所以还需要提供一个静态函数 getInstance() 以获取单例对象句柄,至于具体怎么实现,需要分不同方向(饿汉 or 懒汉)
0
.

#pragma once#include <iostream>class Signal
{
private:// 构造函数私有化Signal(){}// 删除拷贝构造Signal(const Signal&) = delete;public:// 获取单例对象的句柄static Signal* getInstance(){return _sigptr;}void print(){std::cout << "Hello Signal!" << std::endl;}private:// 指向单例对象的静态指针static Signal *_sigptr;
};

注意: 构造函数不能只声明,需要实现,即使什么都不写

为什么要删除拷贝构造?
如果不删除拷贝构造,那么外部可以借助拷贝构造函数,拷贝构造出一个与 单例对象 一致的 “对象”,此时就出现两个对象,这是不符合 单例模式 特点的

为什么要创建一个静态函数?
单例对象也需要被初始化,并且要能被外部使用
调用链逻辑:通过静态函数获取句柄(静态单例对象地址)-> 通过地址调用该对象的其他函数

现在我们来看下饿汉模式

  在程序加载到内存时,就已经早早的把 单例对象 创建好了(此时程序服务还没有完全启动),也就是在外部直接通过 new 实例化一个对象,具体实现如下

#pragma once#include <iostream>// 饿汉模式
class Signal
{
private:// 构造函数私有化Signal(){}// 删除拷贝构造Signal(const Signal&) = delete;public:static Signal* getInstance(){return _sigptr;}void print(){std::cout << "Hello Signal!" << std::endl;}private:// 指向单例对象的静态指针static Signal* _sigptr;
};Signal* Signal::_sigptr = new Signal();

  注:在程序加载时,该对象会被创建

  这里的 单例对象 本质就有点像 全局变量,在程序加载时就已经创建好了

  外部可以直接通过 getInstance() 获取 单例对象 的操作句柄,来调用类中的其他函数

main.cc

#include <iostream>
#include "Signal.hpp"int main()
{Signal::getInstance()->print();return 0;
}

运行结果为
在这里插入图片描述
  这就实现了一个简单的 饿汉版单例类,除了创建 static Signal* 静态单例对象指针 外,也可以直接定义一个 静态单例对象,生命周期随进程,不过要注意的是:getInstance() 需要返回的也是该静态单例对象的地址,不能返回值,因为拷贝构造被删除了;并且需要在类的外部初始化该静态单例对象

#pragma once#include <iostream>// 饿汉模式
class Signal
{
private:// 构造函数私有化Signal(){}// 删除拷贝构造Signal(const Signal&) = delete;public:static Signal *getInstance(){return &_sig;}void print(){std::cout << "Hello Signal!" << std::endl;}private:// 静态单例对象static Signal _sig;
};// 初始化
Signal Signal::_sig;

  饿汉模式 是一个相对简单的单例实现方向,只需要在类中声明,在类外初始化就行了,但它也会带来一定的弊端:延缓服务启动速度

  完全启动服务是需要时间的,创建 单例对象 也是需要时间的,饿汉模式 在服务正式启动前会先创建对象,但凡这个单例类很大,服务启动时间势必会受到影响,大型项目启动,时间就是金钱

  并且由于 饿汉模式 每次都会先创建 单例对象,再启动服务,如果后续使用 单例对象 还好说,但如果后续没有使用 单例对象,那么这个对象就是白创建了,在延缓服务启动的同时造成了一定的资源浪费

  综上所述,饿汉模式 不是很推荐使用,除非图实现简单,并且服务规模较小;既然 饿汉模式 有缺点,就需要改进,于是就出现了 懒汉模式

现在我们来看下懒汉模式

  在 懒汉模式 中,单例对象 并不会在程序加载时创建,而是在第一次调用时创建,第一次调用创建后,后续无需再创建,直接使用即可

#pragma once#include <iostream>// 懒汉模式
class Signal
{
private:// 构造函数私有化Signal(){}// 删除拷贝构造Signal(const Signal&) = delete;public:static Signal *getInstance(){// 第一次调用才创建if(_sigptr == nullptr){_sigptr = new Signal();}return _sigptr;}void print(){std::cout << "Hello Signal!" << std::endl;}private:// 静态指针static Signal *_sigptr;
};// 初始化静态指针
Signal* Signal::_sigptr = nullptr;

  注意: 此时的静态指针需要初始化为 nullptr,方便第一次判断

  饿汉模式 中出现的问题这里全都避免了

  • 创建耗时 -> 只在第一次使用时创建
  • 占用资源 -> 如果不使用,就不会被创建

  懒汉模式 的核心在于 延时加载,可以优化服务器的速度及资源占用

延时加载这种机制就有点像 「写时拷贝」,就赌你不会使用,从而节省资源开销,类似的还有 动态库、进程地址空间 等

  当然,懒汉模式 下也是可以正常使用 单例对象 的

在这里插入图片描述

  这样看来,懒汉模式 确实优秀,实现起来也不麻烦,为什么会说 饿汉模式 更简单呢?

  这是因为当前只是单线程场景,程序暂时没啥问题,如果当前是多线程场景,问题就大了,如果一批线程同时调用 getInstance(),同时认定 _sigptr 为空,就会创建多个 单例对象,这是不合理的

  也就是说当前实现的 懒汉模式 存在严重的线程安全问题

我们现在来证明一下

  简单改一下代码,每创建一个单例对象,就打印一条语句,将代码放入多线程环境中测试

获取单例对象句柄 getInstance() — 位于 Signal 类

static Signal *getInstance()
{// 第一次调用才创建if(_sigptr == nullptr){std::cout << "创建了一个单例对象" << std::endl;_sigptr = new Signal();}return _sigptr;
}

源文件 main.cc

#include "test63.hpp"
#include <iostream>
#include <pthread.h>int main()
{// 创建一批线程pthread_t arr[10];for(int i = 0; i < 10; i++){pthread_create(arr + i, nullptr, [](void*)->void*{// 获取句柄auto ptr = Signal::getInstance();ptr->print();return nullptr;}, nullptr);}for(int i = 0; i < 10; i++)pthread_join(arr[i], nullptr);return 0;
}

在这里插入图片描述
  当前代码在多线程环境中,同时创建了多个 单例对象,因此是存在线程安全问题的

  没有,因为饿汉模式下,单例对象一开始就被创建了,即便是多线程场景中,也不会创建多个对象,它们也做不到

  所以现在我们利用线程互斥锁来保护单例对象的创建

#pragma once#include <iostream>
#include <mutex>// 懒汉模式
class Signal
{
private:// 构造函数私有化Signal(){}// 删除拷贝构造Signal(const Signal&) = delete;public:static Signal* getInstance(){// 加锁保护pthread_mutex_lock(&_mtx);if(_sigptr == nullptr){std::cout << "创建了一个单例对象" << std::endl;_sigptr = new Signal();}pthread_mutex_unlock(&_mtx);return _sigptr;}void print(){std::cout << "Hello Signal!" << std::endl;}private:// 静态指针static Signal *_sigptr;static pthread_mutex_t _mtx;
};// 初始化静态指针
Signal* Signal::_sigptr = nullptr;// 初始化互斥锁
pthread_mutex_t Signal::_mtx = PTHREAD_MUTEX_INITIALIZER;

  注意: getInstance() 是静态函数,互斥锁也要定义为静态的,可以初始化为全局静态锁

  依旧是借助之前的多线程场景,测试一下改进后的 懒汉模式 代码有没有问题
在这里插入图片描述
  结果是没有问题,单例对象 也只会创建一个

  现在还面临最后一个问题:效率问题

  当前代码确实能保证只会创建一个 单例对象,但即使后续不会创建 单例对象,也需要进行 加锁、判断、解锁 这个流程,要知道 加锁 也是有资源消耗的,所以这种写法不妥

  解决方法是在 加锁 前再来一次判断,N个线程,顶多只会进行 N次加锁或解锁,真是极其优雅!!!

static Signal *getInstance()
{// 双检查if(_sigptr == nullptr){// 加锁保护pthread_mutex_lock(&_mtx);if(_sigptr == nullptr){std::cout << "创建了一个单例对象" << std::endl;_sigptr = new Signal();}pthread_mutex_unlock(&_mtx);}return _sigptr;
}

  单纯的 if 判断并不会消耗很多资源,但 加锁 行为会消耗资源,延缓程序运行速度,双检查加锁 可以有效避免这个问题

在这里插入图片描述
  值得一提的是,懒汉模式 还有一种非常简单的新式写法:调用 getInstance() 时创建一个静态单例对象并返回,因为静态单例对象只会初始化一次,所以是可行的,并且在 C++11 之后,可以保证静态变量初始化时的线程安全问题,也就不需要 双检查加锁 了,实现起来非常简单

#pragma once#include <iostream>
#include <mutex>// 懒汉模式
class Signal
{
private:// 构造函数私有化Signal(){}// 删除拷贝构造Signal(const Signal&) = delete;public:static Signal *getInstance(){// 静态单例对象,只会初始化一次,并且生命周期随进程static Signal _sig;return &_sig;}void print(){std::cout << "Hello Signal!" << std::endl;}
};

注意: 静态变量创建时的线程安全问题,在 C++11 之前是不被保障的

  那之前的线程池,你是否可以通过单例模式来进行最终版本优化?

这就交给你来啦!!!

三、其余问题

STL线程安全问题

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

  答案是 不是!

  因为 STL 设计的初衷就是打造出极致性能容器,而加锁、解锁操作势必会影响效率,因此 STL 中的容器并未考虑线程安全,在之前编写的 生产者消费者模型、线程池 中,使用了部分 STL 容器,如 vector、queue、string 等,这些都是需要我们自己去加锁、解锁,以确保多线程并发访问时的线程安全问题

  从另一方面来说,STL 容器种类繁多,容器间实现方式各不相同,无法以统一的方式进行加锁、解锁操作,比如哈希表中就有 锁表、锁桶 两种方式

  所以在多线程场景中使用 STL 库时,需要自己确保线程安全

智能指针线程安全问题

  C++ 标准提供的智能指针有三种:unique_ptr、shared_ptr、weak_ptr

  首先来说 unique_ptr,这是个功能单纯的智能指针,只具备基本的 RAII 风格,不支持拷贝,因此无法作为参数传递,也就不涉及线程安全问题

  其次是 shared_ptr,得益于 引用计数,这个智能指针支持拷贝,可能被多线程并发访问,但标准库在设计时考虑到了这个问题,索性将 shared_ptr 对于引用计数的操作设计成了 原子操作 CAS,这就确保了它的 线程安全,至于 weak_ptr,这个就是 shared_ptr 的小弟,名为弱引用智能指针,具体实现与 shared_ptr 一脉相承,因此它也是线程安全的

其他锁的概念

  悲观锁:总是认为数据会被其他线程修改,于是在自己访问数据前,会先加锁,其他线程想访问时只能等待,之前使用的锁都属于悲观锁

  乐观锁:并不认为其他线程会来修改数据,因此在访问数据前,并不会加锁,但是在更新数据前,会判断其他数据在更新前有没有被修改过,主要通过 版本号机制 和 CAS操作实现

  CAS 操作:当需要更新数据时,会先判断内存中的值与之前获取的值是否相等,如果相等就用新值覆盖旧值,失败就不断重试

  自旋锁:申请锁失败时,线程不会被挂起,而且不断尝试申请锁

  自旋 本质上就是一个不断 轮询 的过程,即不断尝试申请锁,这种操作是十分消耗 CPU 时间的,因此推荐临界区中的操作时间较短时,使用 自旋锁 以提高效率;操作时间较长时,自旋锁 会严重占用 CPU 时间

  自旋锁 的优点:可以减少线程切换的消耗

#include <pthread.h>pthread_spinlock_t lock; // 自旋锁类型int pthread_spin_init(pthread_spinlock_t *lock, int pshared); // 初始化自旋锁int pthread_spin_destroy(pthread_spinlock_t *lock); // 销毁自旋锁// 自旋锁加锁
int pthread_spin_lock(pthread_spinlock_t *lock); // 失败就不断重试(阻塞式)
int pthread_spin_trylock(pthread_spinlock_t *lock); // 失败就继续向后运行(非阻塞式)// 自旋锁解锁
int pthread_spin_unlock(pthread_spinlock_t *lock);

  就这接口风格,跟 mutex 互斥锁 是一脉相承,可以轻易上手,将 线程池 中的 互斥锁 轻易改为 自旋锁

  这就留到我们下篇再来介绍吧~本篇写累了,想结束了


总结

  要结束喽~

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

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

相关文章

Netty 实战篇:Netty RPC 框架整合 Spring Boot,迈向工程化

本文将基于前面构建的 RPC 能力&#xff0c;尝试将其与 Spring Boot 整合&#xff0c;借助注解、自动扫描、依赖注入等机制&#xff0c;打造“开箱即用”的 Netty RPC 框架&#xff0c;提升开发效率与工程规范。 一、为什么要整合 Spring Boot&#xff1f; 手动 new 实例、写注…

Axure中继器学习笔记

一、中继器概述 中继器(Axure Repeater)是Axure中的高级组件&#xff0c;功能类似于数据集成器&#xff0c;主要用于&#xff1a; 数据存储与管理 数据的增删改查操作 数据的分页与展示控制 二、中继器基本使用流程 数据存储&#xff1a;将数据储存在中继器组件中 数据展…

hf-mirror断点续传下载权重

直接浏览器双击一个一个下载 这种方式不支持断点续传 dnf install git-lfs -y 下面成功跳过 LFS 权重下载只拿到 Git 元数据和 LFS 占位符文件了 GIT_LFS_SKIP_SMUDGE1 git clone https://hf-mirror.com/Tongyi-Zhiwen/QwenLong-L1-32B cd QwenLong-L1-32B git lfs install -…

【软件安装那些事 3 】CAD(2026 V60.7z) 安装教程(中文简体版)步骤完整不跳步 { 附软件提取下载链接,永久有效---------百度网盘 }

通过网盘分享的文件&#xff1a;CAD2026 V60.7z 安装包 中文 &#xff08;永久有效&#xff09; 链接: https://pan.baidu.com/s/122UXbOK9iGsD5Ld-lzrfAA?pwdneqd 提取码: neqd 1、解压完成后&#xff0c;打开【Setup】文件夹 2、鼠标右击【Setup】…

RK3399 Android7.1增加应用安装白名单机制

通过设置应用包名白名单的方式限制未授权的应用软件安装。 diff --git a/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java b/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java index af9a533..ca…

体现物联网环境下安全防护的紧迫性 :物联网环境下的个人信息安全:隐忧与防护之道

摘要&#xff1a;随着物联网的飞速发展&#xff0c;个人信息在物联网环境下面临的安全风险日益严峻。本文深入探讨了物联网环境下个人信息泄露的主要途径&#xff0c;分析了当前个人信息安全保护面临的挑战&#xff0c;并从技术、法律、企业责任和个人意识等多方面提出了相应的…

vue3 项目配置多语言支持,如何从服务端拿多语言配置

在 Vue3 项目中实现多语言支持并从服务端获取配置&#xff0c;可以使用 Vue I18n 库。在初始化阶段可以发送请求获取多语言配置或者通过本地文件加载json文件的方式&#xff0c;都可以实现。我这里是tauri项目&#xff0c;所以使用的是invoke从tauri端拿到配置文件&#xff0c;…

使用ssh-audit扫描ssh过期加密算法配置

使用ssh-audit扫描ssh过期加密算法配置 安装检查ssh的加密算法配置修改ssh的加密算法配置 安装 # pip3安装ssh-audit pip3 instal ssh-audit检查ssh的加密算法配置 # 检查ssh的配置 ssh-audit 192.168.50.149修改ssh的加密算法配置 # 查看ssh加密配置文件是否存在 ls /etc/c…

LeetCode 高频 SQL 50 题(基础版)之 【连接】部分 · 下

前五道题&#xff1a;LeetCode 高频 SQL 50 题&#xff08;基础版&#xff09;之 【连接】部分 上 题目&#xff1a;577. 员工奖金 题解&#xff1a; select r.name,b.bonus from Employee r left join Bonus b on r.empIdb.empId where b.bonus <1000 or b.bonus is nul…

[yolov11改进系列]基于yolov11引入感受野注意力卷积RFAConv的python源码+训练源码

[RFAConv介绍] 1、RFAConv 在传统卷积操作中&#xff0c;每个感受野都使用相同的卷积核参数&#xff0c;无法区分不同位置的信息差异&#xff0c;这都限制了网络性能。此外&#xff0c;由于空间注意力以及现有空间注意力机制的局限性&#xff0c;虽然能够突出关键特征&#xf…

【软件设计】通过软件设计提高 Flash 的擦写次数

目录 0. 个人简介 && 授权须知1. Flash 和 EEROM 基本情况2. 场景要求3. 软件设计思路4. 代码展示4.1 flash.h4.2 flash.c 0. 个人简介 && 授权须知 &#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是喜欢记录零碎知识点的菜鸟…

OpenCV CUDA模块直方图计算------在 GPU 上计算输入图像的直方图(histogram)函数histEven()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该函数用于在 GPU 上计算输入图像的直方图&#xff08;histogram&#xff09;。它将像素值区间均匀划分为若干个 bin&#xff08;桶&#xff09;…

龙虎榜——20250530

上证指数阳包阴&#xff0c;量能较前期下跌有放大&#xff0c;但个股跌多涨少&#xff0c;下跌超过4000个。 深证指数和上涨总体相同。 2025年5月30日龙虎榜行业方向分析 1. 医药&#xff08;创新药原料药&#xff09; 代表标的&#xff1a;华纳药厂、舒泰神、睿智医药、华…

HarmonyNext使用request.agent.download实现断点下载

filedownlaod(API12) &#x1f4da;简介 filedownload 这是一款支持大文件断点下载的开源插件&#xff0c;退出应用程序进程杀掉以后或无网络情况下恢复网络后&#xff0c;可以在上次位置继续恢复下载等 版本更新—请查看更新日志!!! 修复已知bug,demo已经更新 &#x1f4d…

nginx: [emerg] bind() to 0.0.0.0:80 failed (10013: 80端口被占用

Nginx启动报错&#xff1a;nginx: [emerg] bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a socket in a way forbidden by its access permissions) 这个报错代表80端口被占用 先查看占用80的端口 netstat -aon | findstr :80 把它杀掉&#xff0c;强…

embbeding 视频截图

Embedding是什么&#xff1f;有什么作用&#xff1f;是怎么得到的&#xff1f;_哔哩哔哩_bilibili

服务器tty2终端如何关机

在服务器的 tty2 或其他虚拟终端上&#xff0c;要安全地进行关机操作&#xff0c;可以使用以下命令之一&#xff1a; 1.1 使用 shutdown 命令&#xff1a; shutdown 命令可以计划系统关机。默认需要超级用户权限。 sudo shutdown -h now-h 选项表示关机&#xff08;halt&…

时序数据库IoTDB启动方式及集群迁移指南

IoTDB启动方式 IoTDB在配置启动时有两种推荐方式&#xff1a; ‌主机名启动‌&#xff1a; ‌推荐理由‌&#xff1a;主机名启动方式更为灵活&#xff0c;便于在不同网络环境中部署相同的IoTDB实例。‌工作原理‌&#xff1a;IoTDB启动后会维护一张节点编号与网络地址的映射表…

如何在Qt中绘制一个带有动画的弧形进度条?

如何在Qt中绘制一个弧形的进度条 在图形用户界面开发中&#xff0c;进度指示控件&#xff08;Progress Widget&#xff09;是非常常见且实用的组件。CCArcProgressWidget 是一个继承自 QWidget 的自定义控件&#xff0c;用于绘制圆弧形进度条。当然&#xff0c;笔者看了眼公开…

在 Mac 下 VSCode 中的终端使用 option + b 或 f 的快捷键变成输入特殊字符的解决方案

前言 在终端里&#xff0c;我们可以使用 option b 和 option f 来在我们输入的命令中进行快速的前后调整光标&#xff0c;但是&#xff0c;在未设置的情况下&#xff0c;在 MacOS 中&#xff0c;会变成输入特殊字符。 普通键盘上是 alt b 和 alt f &#xff0c;只是叫法不…