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

linux线程 | 同步与互斥 | 线程池以及知识点补充

        前言:本节内容是linux的线程的相关知识。本篇首先会实现一个简易的线程池, 然后再将线程池利用单例的懒汉模式改编一下。 然后再谈一些小的知识点,比如自旋锁, 读者写者问题等等。 那么, 现在开始我们的学习吧。

        ps:本节内容设计到了单例模式, 建议了解单例设计模式以及多线程, 生产消费者模型的友友们进行观看哦。

目录

 线程池

 什么是线程池

线程池的应用场景

代码实现 

准备文件

makefile

Task.h

ThreadPool.h

main.cpp

运行结果

单例模式

常见的锁

自旋锁

自旋锁接口

读者写者问题 

概念

 接口

理解


 线程池

 什么是线程池

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

线程池的应用场景

  •         需要大量的线程来完成任务,且完成任务的时间比较短。 单个任务小, 任务数量大更加适合!对于任务很大(时间很长)的场景, 线程池的有点就不明显了。 因为如果一个任务很大的话, 那这个任务比创建线程的时间长的多, 就不明显。 
  •         对于性能要求比较苛刻, 比如要求服务器迅速相应客户请求。 
  •         接受突发性的大量请求, 但不至于使服务器因此产生大量线程的引用。 突发性大量客户请求, 在没有线程池情况下, 将产生大量线程, 虽然理论上大部分操作系统县城数据最大值不是问题, 短时间内产生大量线程可能使内存到达极限, 出现错误。 

代码实现 

准备文件

makefile

main.exe:main.cppg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -rf main.exe

Task.h

#include <iostream>
using namespace std;
#include <vector>
#include <string>//加减乘除
string opers = "+-*/%";//Task.h文件里面包含了任务类, 这个是我们线程池要执行的任务
class Task
{
public://构造函数, 第一个参数data1, 第二个参数data2, 第三个参数是加减乘除的符号。 这个任务就是进行四则运算Task(int x, int y, char op): data1_(x), data2_(y), op_(op){}~Task() {}//执行任务的接口run(), 这个方法对三个变量进行判断, 然后进行运算。 void run(){switch (op_){case '+':cout << data1_ << "+" << data2_ << "=" << data1_ + data2_ << endl;break;case '-':cout << data1_ << "-" << data2_ << "=" << data1_ - data2_ << endl;break;case '*':cout << data1_ << "*" << data2_ << "=" << data1_ * data2_ << endl;break;case '/':if (data2_ == 0){cout << "error, " << data1_ << '/' << data2_ << " is error!" << endl;}else{cout << data1_ << "/" << data2_ << "=" << data1_ / data2_ << endl;}break;case '%':if (data2_ == 0){cout << "error, " << data1_ << '%' << data2_ << " is error!" << endl;}else{cout << data1_ << "%" << data2_ << "=" << data1_ % data2_ << endl;}break;default:cout << "default" << endl;break;}}//仿函数, 为了方便我们的对象能够像函数一样使用。 void operator()(){run();}private://每一个任务对象里面都有三个参数, 一个data1, 一个data2, 最后一个op_int data1_;int data2_;char op_;
};

ThreadPool.h


#pragma once
#include<iostream>
#include<pthread.h>
#include<vector>
#include<string>
using namespace std;
#include<queue>
#include<ctime>
#include<unistd.h>//对线程的属性做一下封装, 有利于线程池的保存以及后面的处理
struct ThreadInfo
{pthread_t tid_;string name_;   
};template<class T>
class ThreadPool
{static const int defaultnum = 5;  //默认的线程池的大小(线程池的大小就是里面包含的线程的数量)private://加锁解锁void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}//唤醒线程, 线程是可以被挂起的(就比如信号量)。 当任务没有的时候,线程就要被挂起, 有任务后再唤醒void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}public://线程要执行的函数static void* HandlerTask(void* args){ThreadPool<T>* tp = static_cast<ThreadPool<T>*>(args);while(true) {tp->Lock();while (tp->tasks_.empty()){tp->ThreadSleep(); //如果队列里面没有任务了, 就让线程去休眠。}//否则就去拿到tasks里面的任务T t = tp->tasks_.front();tp->tasks_.pop();//tp->Unlock();t();  //每一个线程先对任务进行消费, 消费完成之后处理任务。    }}//运行这个线程池, 也就是先将线程创建出来。 然后去运行线程void Start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name_ = "thread-";threads_[i].name_ += to_string(i);   pthread_create(&(threads_[i].tid_), nullptr, HandlerTask, this);}}//主线程给线程池发送任务, 注意, 这个任务一定是可以被储存起来的。 因为当我们的任务很多很多的时候, 我们的线程池内的线程要一个一个地对这些任务进行处理void Push(const T& t){Lock();tasks_.push(t);Wakeup();Unlock();}//线程池地初始化, 就是将锁和条件变量都初始化一下ThreadPool(int num = defaultnum):threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}//析构~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}private:vector<ThreadInfo> threads_;   //线程都维护在vector当中, 这个就是线程池里面的线程的个数,queue<T> tasks_ ;              //向线程池中发送任务, 这个队列里面保存的就是我们的任务的数目。 pthread_mutex_t mutex_;        //锁,用来生产者线程(本份代码只是主线程)给线程池发送任务时候加锁使用以及消费者线程抢夺任务时加锁使用 pthread_cond_t cond_;          //条件变量, 用来没有任务的时候,消费者要挂起。 };

main.cpp

#include"ThreadPool.h"
#include"Task.h"int main()
{//运行线程池ThreadPool<Task>* tp = new ThreadPool<Task>();tp->Start();    srand(time(nullptr) ^ getpid());while (true){//构建任务  int x = rand() % 10 + 1;usleep(10);int y = rand() % 5;char op = opers[rand()% opers.size()];Task t(x, y, op);//交给线程池处理//主线程给线程池发送任务, 其实就相当于主线程时生产者。 tp->Push(t);sleep(1);}return 0;
}

运行结果

单例模式

        利用单例模式来创建线程池

主要改动就是ThreadPool.h


#pragma once
#include<iostream>
#include<pthread.h>
#include<vector>
#include<string>
using namespace std;
#include<queue>
#include<ctime>
#include<unistd.h>//对线程的属性做一下封装, 有利于线程池的保存以及后面的处理
struct ThreadInfo
{pthread_t tid_;string name_;   
};template<class T>
class ThreadPool
{static const int defaultnum = 5;  //默认的线程池的大小(线程池的大小就是里面包含的线程的数量)private://加锁解锁void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}//唤醒线程, 线程是可以被挂起的(就比如信号量)。 当任务没有的时候,线程就要被挂起, 有任务后再唤醒void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}public://线程要执行的函数static void* HandlerTask(void* args){ThreadPool<T>* tp = static_cast<ThreadPool<T>*>(args);while(true) {tp->Lock();while (tp->tasks_.empty()){tp->ThreadSleep(); //如果队列里面没有任务了, 就让线程去休眠。}//否则就去拿到tasks里面的任务T t = tp->tasks_.front();tp->tasks_.pop();//tp->Unlock();t();  //每一个线程先对任务进行消费, 消费完成之后处理任务。    }}//运行这个线程池, 也就是先将线程创建出来。 然后去运行线程void Start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name_ = "thread-";threads_[i].name_ += to_string(i);   pthread_create(&(threads_[i].tid_), nullptr, HandlerTask, this);}}//主线程给线程池发送任务, 注意, 这个任务一定是可以被储存起来的。 因为当我们的任务很多很多的时候, 我们的线程池内的线程要一个一个地对这些任务进行处理void Push(const T& t){Lock();tasks_.push(t);Wakeup();Unlock();}//线程池地初始化, 就是将锁和条件变量都初始化一下ThreadPool(int num = defaultnum):threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}//析构~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}//获取单例//改编成单例的步骤里面只有这里要说一下, 就是为什么我们要套双层判断。 其实这里的最外面的一层的判断是我们另外加上去的。 为什么//要加这个判断呢? 就是如果我们不加最外层这一层判断。 那么每一个线程获取单例都要申请所,加锁。 不就是相当于所有的线程都在串行执行? 这就有效率问题。 //解决方案就是这个再加一层判断。 这样假如有四个线程。 那么一开始四个线程都在判断, 那么它们四个线程都进入了if里面。 然后就都申请锁, 但是只有第一个线程能够//进入第二层里面, 其他的进入不了。 那么当这一轮的四个线程都申请一次锁候就都退出了函数, 然后就都去做自己的事情了。 问题是, 当下次它们再来申请单例对象的时候它们连//第一层判断都成功不了了, 也就都不用加锁解锁了, 这就大大提高了效率!!!static ThreadPool<T>* GetInstance(int num = defaultnum){if (tp_ == nullptr){pthread_mutex__lock(&lock_);if (tp_ == nullptr) {tp_ = new ThreadPool<T>(num);}pthread_mutex_unlock(&lock_);}return tp_;}private://构造函数私有化, 只有Getinstance里面才能创建。 ThreadPool(int num = defaultnum):threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}//单例模式只有一个对象, 所以要将拷贝构造和拷贝赋值封住, 为了防止有人在外部重新拷贝一个对象。 ThreadPool(const ThreadPool<T>& tp) = delete;const ThreadPool<T>& operator=(const ThreadPool<T>& tp) = delete;~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}private:vector<ThreadInfo> threads_;   //线程都维护在vector当中, 这个就是线程池里面的线程的个数,queue<T> tasks_ ;              //向线程池中发送任务, 这个队列里面保存的就是我们的任务的数目。 pthread_mutex_t mutex_;        //锁,用来生产者线程(本份代码只是主线程)给线程池发送任务时候加锁使用以及消费者线程抢夺任务时加锁使用 pthread_cond_t cond_;          //条件变量, 用来没有任务的时候,消费者要挂起。 pthread_mutex_t lock_;         //锁, 这个锁是为了在获取单例的时候能够让线程原子性的访问if (tp_ == nullptr)。static ThreadPool<T>* tp_;    //tp指针, 这就是唯一个单例对象。 };template<class T>
ThreadPool<T>* ThreadPool<T>::tp_ = nullptr;    

 运行结果和上次一样:

常见的锁

  •         悲观锁:我们平时写的锁都是悲观锁。 就是总是担心其他线程来修改数据, 所以在线程访问数据之前都要先加锁。 就比如互斥锁和信号量。
  •         乐观锁:猜测线程在访问资源的时候不会有其他的线程,因此不上锁。但是在更新数据之前会判断其他线程在更新前有没有对数据进行修改。 
  •         CAS:当需要更新时,判断当前内存值和之前取得的值是否相等。 如果相等则用新值更新, 若不相等则失效。
  •         自旋锁:这个要着重谈一下, 后面很长一段论述都是在谈他。

自旋锁

在讲自旋锁之前, 博主要将在讲解进程等待的时候的故事再重新讲一下:       

  •         就是假设小王今天吃饭的时候想起来明天要考试了。 然后小王就很慌, 很慌怎么办呢。 所以小王就想到了他的好朋友小明。 小明是一个努力型学霸, 然后小王就找到他让他带他复习明天的考试, 小王请吃饭。 所以呢, 小明就很痛快地答应了。 但是小明要先读一个小时的书, 小明一听可以啊。 之后小明就去楼上读书了, 小王呢, 要知道我们现在是非常强调效率的!所以小王也不在原地等, 他说他去学校门口的网吧上会网, 然后他就去上网了。 小明读完书之后就给小王打了一个电话, 小王接到电话后立马又回来接着小明去吃饭了。——这是故事一
  •         在小明的帮助下, 小王疯狂的复习了一天一夜。 最后小王考试的时候成功考了60分, 过了。 几天之后小王又从别人的口中得知两天后又要考网络, 然后小王就又去请教小明。 小明就说, 好的, 马上我就从楼上下来, 我俩一起去自习室。 请问, 小王这个时候还回去网吧上网吗? 不会的!小王不去网吧了! 但是呢,过了十秒钟小王就给小明打了电话问下来了没有, 小明说马上。 又过了十秒钟, 小王又打电话问下来了没有, 小明说马上。 又过了十秒钟......, 过了很多个十秒钟之后, 小明终于下来了。 于是两个人愉快的又去吃饭了。 ——这是故事二。

        在上面的故事当中, 我们把小明当成临界区代码, 那么小王在楼下等小明和去网吧等小明取决于什么? 是不是就是取决于小明的时常? 我们前面一直都在研究临界区的线程安全问题。 但是我们几乎没有研究过线程在临界区待得时间的长短的问题。 等待什么呢? 等待刚刚进去的线程什么时候出来。 那么, 我们如果一个线程在临界区内待得时间非常长。 那么其他现车给在申请锁之后最好的做法是不是就是挂起? 如果一个线程在临界区待得时间非常短, 我们此时可以选择让其他线程在此时不要选择挂起, 而是处于一种自旋状态。 而我们的小王每十秒就打个电话的过程其实就是自选的过程。 所以我么以前学习的所有锁, 全部都是挂起等待锁。 一个线程进入, 其他的线程竞争失败, 失败的时候他们就把自己挂起等待了。 挂起等待要不要花时间? 挂起的时候就是将我们的执行流放到我们的等待队列里面, 然后唤醒的时候又把它们从等待队列里面拆下来放到cpu里面去执行。 这个过程来回对数据结构做迁移, 是要花时间的。 ——所以, 什么是自旋, 自旋就是不把自己挂起, 而是由线程不断地去周而复始的去申请锁。 如果申请锁成功则进入, 失败则返回重新检测锁的状态。 

自旋锁接口

        其实我们可以自己实现自旋锁, 就是使用trylock, trylock就是尝试检测这个锁, 如果这个锁没有申请成功, 就出错返回。 但是lock就是直接挂起, 所以我们想要实现自旋锁就可以使用while循环然后trylock。 

        也可以使用系统给我们提供的接口

        可以自己使用spin_lock, 这个函数就会对一个锁返回申请, 直到申请成功。 spin_trylock就是和mutex_trylock一样,都是申请失败了就直接返回, 想要自选需要自己加。

读者写者问题 

概念

        生活中有哪些场景是读者写者问题呢?

        我们在初中小学画的板报、作家写的小说、甚至博主写的这篇博客都是读者写者的问题。 就比如我们写一篇博客, 我们写博客的人就是写者, 然后读这篇博客的人就是读者。 

        对于读者写者问题来说, 也是要遵守321原则——三种关系、两种角色、一个交易场所。

  •         三种关系:写者和读者(互斥竞争)、读者和读者(共享关系)、写者和读者(互斥和同步)
  •         两种角色:写者和读者
  •         一个交易场所:数据交换的地点

        为什么读者和读者之间是共享关系, 为什么消费者和消费者之间确是互斥关系呢?

  •         因为写者把数据写进去了, 读者并不会把数据拿走!!!
  •         但是生产者生产了数据之后,消费者是会把数据拿走的!!!

 接口

pthread_rwlock_t是读写锁的类型, 然后上图两个函数就是对锁进行初始化和对锁进行销毁。

rdlock是以读者的身份进行加锁, 另外我们的wlock就是以写者的方式进行加锁。

 

然后呢, 我们的解锁时使用统一的接口进行解锁。

理解

        一般而言, 读多写少。 如果有一个写者在写, 任何读者都不能进来。 而一个读者正在读, 任何写者都不能进来。  所以, 正常来讲, 读者争锁的能力比写者要强。 所以在我们的读者写者当中, 我们往往会出现读者很多, 而写者很少的情况。 所以竞争锁的时候我们的读者竞争到锁的概率是非常的高的, 进而导致写者长时间得不到锁而产生饥饿问题。

        饥饿问题是一个中性的现象还是一个偏向编译的现象呢? 在读者写者模型这里是一个比较偏向中性的词语。 因为他就是一个事实, 因为读者本来就多, 读者本来就多写者本来就少, 所以就注定了根据这种场景是默认的现象。 那么这种默认的行为我们叫做读者优先。 

现在我们实现一下读者优先的伪代码

首先就是读者的加锁和解锁操作:
lock(&rlock);
reader_count++;
if (reader_count == 1) lock(&wlock);  //reader_count == 1说明这是第一个读者, 从这里开始就让读者进不来!!!
unlock(&rlock);//读者进来了之后, 就进行读取写者的数据
//都读取完成之后就离开
lock(&rlock);
reader_count--;
if (reader_count == 0) unlock(wlock); //如果是最后一个读者, 就让写者进来,就可以写数据了。 
unlock(&rlock);然后是写者的加锁和解锁,写者的比较简单,因为写者少, 竞争锁的能力弱
lock(&wlock);//写入unlock(&wlock);

——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!   

相关文章:

linux线程 | 同步与互斥 | 线程池以及知识点补充

前言&#xff1a;本节内容是linux的线程的相关知识。本篇首先会实现一个简易的线程池&#xff0c; 然后再将线程池利用单例的懒汉模式改编一下。 然后再谈一些小的知识点&#xff0c;比如自旋锁&#xff0c; 读者写者问题等等。 那么&#xff0c; 现在开始我们的学习吧。 ps:本…...

ArkTS 如何实现表单,地区选择效果

速览 ArkTS实现表单和地区选择效果,可通过Picker组件实现地区选择下拉列表,结合表单组件如Input等构建完整表单。使用ArkTS提供的UI组件库和状态管理机制,可以方便地构建复杂且交云互动的表单界面。 1. ArkTS 表单基础 在ArkTS中,构建表单通常涉及多个UI组件的组合,如I…...

Vite 项目的核心配置- vite.config.ts 和 tsconfig.json 全解析

一、vite.config.ts 详细说明 vite.config.ts 是 Vite 项目的核心配置文件。它允许你自定义 Vite 的行为,以适应你的项目需求。 让我们来看看其中一些重要的配置选项: import { fileURLToPath, URL } from node:url// 使用 defineConfig 帮手函数&#xff0c;这样不用 jsdoc …...

如何使用JMeter进行性能测试的保姆级教程

性能测试是确保网站在用户访问高峰时保持稳定和快速响应的关键环节。作为初学者&#xff0c;选择合适的工具尤为重要。JMeter 是一个强大的开源性能测试工具&#xff0c;可以帮助我们轻松模拟多用户场景&#xff0c;测试网站的稳定性与性能。本教程将引导你通过一个简单的登录场…...

Qt 实战(11)样式表 | 11.1、样式表简介

文章目录 一、样式表简介1、简介2、样式表语法2.1、样式规则2.2、选择器类型2.3、伪状态2.4、设置子控件状态 3、样式表继承与优先级3.1、样式表继承3.2、样式表优先级3.3、解决冲突3.4、样式表层叠 4、总结 前言&#xff1a; 在开发图形用户界面&#xff08;GUI&#xff09;应…...

WebGl 多缓冲区和数据偏移

1.多缓冲区 多缓冲区技术通常涉及到创建多个缓冲区对象&#xff0c;并将它们用于不同的数据集。这种做法可以提高数据处理效率&#xff0c;尤其是在处理大量数据或需要频繁更新数据时。通过预先分配和配置多个缓冲区&#xff0c;可以在不影响渲染性能的情况下&#xff0c;快速…...

基于SSM的甜品店销售管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

Spacetime Gaussian Feature Splatting for Real-Time Dynamic View Synthesis

Spacetime Gaussian Feature Splatting for Real-Time Dynamic View Synthesis 摘要 动态场景的新视角合成一直是一个引人入胜但充满挑战的问题。尽管最近取得了很多进展&#xff0c;但如何同时实现高分辨率的真实感渲染、实时渲染和紧凑的存储&#xff0c;依然是一个巨大的…...

PCL 基于FPFH特征描述子获取点云对应关系

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 FPFH特征计算函数 2.1.2 获取点云之间的对应点对函数 2.1.3 可视化函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接: PCL点云算法与项目实战案例汇总…...

项目实战:Qt+OpenCV仿射变换工具v1.1.0(支持打开图片、输出棋盘角点、调整偏移点、导出变换后的图等等)

若该文为原创文章&#xff0c;转载请注明出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/143105881 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、Op…...

OpenCV坐标系统与图像处理案例

在图像处理中&#xff0c;理解图像的坐标系统是至关重要的。OpenCV&#xff0c;作为一个强大的计算机视觉库&#xff0c;提供了丰富的功能来操作图像。本文将介绍OpenCV中的坐标系统&#xff0c;并提供一个简单的案例来展示如何使用这些坐标来修改图像的特定区域。 OpenCV坐标…...

Unity之如何使用Unity Cloud Build云构建

文章目录 前言什么是 UnityCloudBuild?如何使用Unity云构建Unity 团队中的人员不属于 Unity Team 的人员UnityCloudBuild2.0价格表如何使用Unity云构建配置CloudBuild前言 Unity Cloud Build作为Unity平台的一项强大工具,它允许开发团队通过云端自动构建项目,节省了繁琐的手…...

Halcon开启多线程

并行运算&#xff08;提升检测时间&#xff09; 支持主线程中的子线程并行执行程序和调用算子。 一旦启动&#xff0c;子线程由线程 ID 标识&#xff0c;该线程 ID 是一个取决于操作系统的整数进程号。 子线程的执行独立于它们启动的线程。 因此&#xff0c;无法预测子线程执行…...

Echarts 点击事件无法使用 this 或者 this绑定的数据无法获取

这里写自定义目录标题 现象解决方案 现象 给echarts绑定自定义点击事件时&#xff0c;无法使用this&#xff0c;并且无法获取到this绑定的数据。 解决方案 增加&#xff1a;const _this this; 代码块如下&#xff1a; const _this this; let myChart echarts.init(docum…...

PCL 基于距离阈值去除错误对应关系(永久免费版)

目录 一、概述1.1 原理1.2 实现步骤1.3应用场景 二、关键函数2.1 获取初始点对2.2 基于距离的对应关系筛选函数2.3 可视化 三、完整代码四、结果展示 即日起&#xff0c;付费专栏所有内容将以永久免费形式陆续进行发表&#xff01;&#xff01;&#xff01; 一、概述 在3D点云的…...

DirectX 11 和 Direct3D 11 的关系

以下是对两者的详细比较&#xff1a; DirectX 11 DirectX 11是微软的一项技术&#xff0c;为高性能游戏和复杂图形程序制定了标准。它是DirectX系列的一个版本&#xff0c;引入了多项创新功能&#xff0c;如硬件加速的Tessellation&#xff08;细分曲面技术&#xff09;、多线…...

什么是SCRM?为什么企业要做SCRM?

很多人都知道CRM是客户关系管理系统&#xff0c;而SCRM又是什么呢&#xff1f; 今天我就给大家用一文讲清SCRM的那些事&#xff0c;本文包括&#xff1a;SCRM 的定义与内涵&#xff0c;与传统 CRM 的区别&#xff1b;通过案例阐述其重要性及作用&#xff0c;如适应消费模式转变…...

类间方差,分割地物

类间方差&#xff08;Inter-class Variance&#xff09;是用于图像分割中的一种统计量&#xff0c;特别是在使用Otsu方法进行阈值选择时。它衡量的是分割后两个类别&#xff08;通常是前景和背景&#xff09;之间的分离程度。类间方差越大&#xff0c;说明两个类别之间的差异越…...

基于微博评论的自然语言处理情感分析

目录 一、项目概述 二、需要解决的问题 三、数据预处理 1、词汇表构建&#xff08;vocab_creat.py&#xff09; 2、数据集加载&#xff08;load_dataset.py&#xff09; 四、模型构建&#xff08;TextRNN.py&#xff09; 1、嵌入层&#xff08;Embedding Layer&#xff…...

MFEM( Modular Finite Element Methods)是一个灵活的、可扩展的、开源的有限元库

MFEM( Modular Finite Element Methods )是一个灵活的、可扩展的、开源的有限元库,主要用于求解偏微分方程(PDE)问题。MFEM的目标是通过模块化设计和强大的抽象能力,简化有限元方法的使用,并支持高效的并行计算,尤其是在复杂的几何形状和自适应网格细化的情况下。 核…...

在VMware上创建虚拟机以及安装Linux操作系统,使用ssh进行远程连接VMware安装注意点 (包含 v1,v8两张网卡如果没有的解决办法)

一&#xff0c;VMware上创建虚拟机 1.VMware下载 1&#xff09;点击VMware官网进入官网 网址:VMware by Broadcom - Cloud Computing for the EnterpriseOptimize cloud infrastructure with VMware for app platforms, private cloud, edge, networking, and security.https…...

关于vue3中如何实现多个v-model的自定义组件

实现自定义组件<User v-model"userInfo" v-model:gender"gender"></User> User组件中更改数据可以同步更改父组件中的数据&#xff1a; 1 父组件&#xff1a; <User v-model"userInfo" v-model:gender"gender">&…...

【STM32项目_2_基于STM32的宠物喂食系统】

摘要&#xff1a;本文介绍一款基于 STM32 的宠物喂食系统资源。该系统以 STM32 为核心&#xff0c;集成多种传感器与设备&#xff0c;涵盖 DHT11、HX711、减速马达及 ESP8266 模块&#xff0c;具备环境监测、精准喂食、网络连接及数据存储功能。 &#x1f51c;&#x1f51c;&am…...

商场楼宇室内导航系统

商场楼宇室内导航系统 本文所涉及所有资源均在传知代码平台可获取 文章目录 商场楼宇室内导航系统效果图导航效果图查看信息数据加载加载模型模型选型处理楼层模型绑定店铺创建店铺名称动态显示隐藏2d元素空气墙查看信息楼梯导航效果图 导航效果图 查看信息 数据加载 因为是一…...

2025全网最全计算机毕业设计选题推荐:计算机毕设选题指导及避坑指南√

博主介绍&#xff1a;✌全网粉丝50W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HLM…...

Vision China 2024 | 移远通信以一体化的AI训练及部署能力,引领3C电子制造智能升级

10月14日&#xff0c;由机器视觉产业联盟(CMVU)主办的中国机器视觉展(Vision China)在深圳国际会展中心盛大开幕。作为全球领先的物联网整体解决方案供应商&#xff0c;移远通信应邀参加展会首日举办的“智造引领数质并进”3C电子制造自动化与数字化论坛。 论坛上&#xff0c;移…...

浏览器播放rtsp视频流解决方案

方案一: html5 websocket_rtsp_proxy 实现视频流直播 实现原理 实现步骤 服务器安装streamedian服务器 客户端通过video标签播放 <video id"test_video" controls autoplay></video><script src"free.player.1.8.4.js"></script&g…...

Ubuntu下查看指定文件大小

Ubuntu下查看指定文件大小 方法一&#xff1a;查看指定文件夹的总大小方法二&#xff1a;查看文件夹内各个子文件夹的大小方法三&#xff1a;查看指定深度的子文件夹大小方法四&#xff1a;使用ls命令查看单个文件的大小注意事项 在Ubuntu中查看指定文件夹的大小&#xff0c;你…...

【南开X上海交大】OPUS:效率显著提升的OCC网络

1. 摘要 占据预测任务旨在预测体素化的3D环境中的占据状态&#xff0c;在自动驾驶领域中迅速获得了关注。主流的占据预测方法首先将3D环境离散化为体素网格&#xff0c;然后在这些密集网格上执行分类。然而&#xff0c;样本数据分析显示&#xff0c;大多数体素实际上是未占据的…...

SqlUtils 使用

一、前言 随着 Solon 3.0 版本发布&#xff0c;新添加的 SqlUtils 接口&#xff0c;用于操作数据库&#xff0c;SqlUtils 是对 Jdbc 原始接口的封装。适合 SQL 极少或较复杂&#xff0c;或者 ORM 不适合的场景使用。 二、SqlUtils 使用 1、引入依赖 <dependency><…...