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

【Linux】线程——线程池、线程池的实现、线程安全的线程池、单例模式的概念、饿汉和懒汉模式、互斥锁、条件变量、信号量、自旋锁、读写锁

文章目录

  • Linux线程
    • 7. 线程池
      • 7.1 线程池介绍
      • 7.2 线程池的实现
      • 7.3 线程安全的线程池
        • 7.3.1 单例模式的概念
        • 7.3.2 饿汉和懒汉模式
    • 8. 常见锁使用汇总
      • 8.1 互斥锁(Mutex)
      • 8.2 条件变量(Condition Variable)
      • 8.3 信号量(Semaphore)
      • 8.4 自旋锁(Spin Lock)
      • 8.5 读写锁(Read-Write Lock)

Linux线程

7. 线程池

  线程池是一种多线程编程中的技术和概念。

  它是一种线程使用模式。是一组预先创建好的线程集合,这些线程处于等待状态,随时准备接受任务并执行。

在这里插入图片描述

  

7.1 线程池介绍

为什么使用线程池

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

  

线程池的应用场景

  (1)需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

  (2)对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

  (3)接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。 突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

  

使用线程池的优点

  (1)提高性能:避免了频繁创建和销毁线程的开销,因为线程的创建和销毁是比较耗时的操作。

  (2)控制资源:可以限制线程的数量,防止过多的线程竞争系统资源,导致系统性能下降甚至崩溃。

  (3)提高响应性:能够更快地响应新的任务请求,因为线程已经准备好,无需等待线程创建。

  

7.2 线程池的实现

线程池示例

  (1)创建固定数量线程池,循环从任务队列中获取任务对象。

  (2)获取到任务对象后,执行任务对象中的任务接口。

  

执行任务:

#pragma once
#include <iostream>
#include <string>std::string opers="+-*/%";enum{DivZero=1,ModZero,Unknown
};class Task
{
public:Task(){}Task(int x,int y,char op):_data1(x),_data2(y),_oper(op),_result(0),_exitcode(0){}void run(){switch (_oper){case '+':_result=_data1+_data2;break;case '-':_result=_data1-_data2;break;case '*':_result=_data1*_data2;break;case '/':{if(_data2==0) _exitcode=DivZero;else _result=_data1/_data2;}break;case '%':{if(_data2==0) _exitcode=ModZero;else _result=_data1%_data2;}break;default:_exitcode=Unknown;break;}}//Task对象重载运算符(),()直接进行run函数void operator()(){run();}std::string GetResult(){std::string r=std::to_string(_data1);r+=_oper;r+=std::to_string(_data2);r+="=";r+=std::to_string(_result);r+="[code: ";r+=std::to_string(_exitcode);r+="]";return r;}std::string GetTask(){std::string r=std::to_string(_data1);r+=_oper;r+=std::to_string(_data2);r+="=?";return r;}~Task(){}private: int _data1;int _data2;char _oper;int _result;int _exitcode;
};

  

线程池:

#pragma once#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include "Task.hpp"struct ThreadData
{pthread_t tid;std::string name;
};static const int defaultnum=5; //默认线程数量//实现我们的线程池
template<class T>
class ThreadPool
{
public: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);}bool IsQueueEmpty() {return _tasks.empty();}public://注意我们线程调用的函数要求参数和返回值都是void*//但是handler在类中默认有this指针->参数不匹配,可以bind或者声明static或放在类外static void *Handler(/*ThreadPool *this,*/void *args){ThreadPool<T> *tp=static_cast<ThreadPool<T>*>(args);while(true){tp->Lock();while(tp->IsQueueEmpty()) //判断任务是否为空{tp->ThreadSleep(); //条件变量}T t=tp->Pop(); //取出任务tp->Unlock();t(); //处理任务 std::cout<<" run, "<<"result: "<< t.GetResult()<<std::endl;            }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,Handler,this);}}void Push(const T &t) //向任务队列放入任务{Lock();_tasks.push(t); //放入任务Wakeup(); //唤醒线程Unlock();}T Pop() //取出任务{T t=_tasks.front();_tasks.pop();return t;}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:   std::vector<ThreadData> _threads; //线程池std::queue<T> _tasks; //任务队列pthread_mutex_t _mutex; //锁pthread_cond_t _cond; //条件变量
};

  

运行函数:

#include <iostream>
#include "ThreadPool.hpp"int main()
{ThreadPool<Task> *tp=new ThreadPool<Task>(5);tp->Start();srand(time(nullptr) ^ getpid());    while(true){//1. 构建任务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);//2. 交给线程池处理std::cout<<"main thread make task: "<<t.GetTask()<<std::endl;sleep(1);}return 0;
}

  

在这里插入图片描述

  

7.3 线程安全的线程池

7.3.1 单例模式的概念

  单例模式是一种常见的软件设计模式

  概念:单例模式确保一个类只有一个实例存在,并提供一个全局访问点来获取该实例。

  

特点包括

  唯一性:保证一个类在整个应用程序中只有一个实例。

  全局访问:提供了一种全局访问这个唯一实例的方式,方便在程序的任何地方使用。

  延迟初始化:通常实例的创建是延迟的,即在首次使用时才创建实例,以提高性能和资源利用率。

  

单例模式的优点

  节省系统资源:避免了频繁创建和销毁对象带来的资源消耗。

  统一管理:对唯一的实例进行集中管理和控制,方便维护和修改。

  保证一致性:在整个应用中,对于共享的数据或状态,通过单例模式可以保证其一致性。

  

7.3.2 饿汉和懒汉模式

  饿汉模式:

  在类加载时就创建单例对象。

  优点:线程安全, 因为对象在类加载时就已经创建好了,不存在多线程并发创建的问题。简单直接,实现较为简单。

  缺点:无论是否使用,对象都会在类加载时创建,可能会造成一定的资源浪费。

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

  

  懒汉模式:

  在第一次使用时才创建单例对象。

  优点:延迟对象的创建,只有在真正需要时才创建,节省了资源

  缺点:线程不安全,在多线程环境下可能会创建多个实例。需要额外的处理来保证线程安全,增加了实现的复杂性。

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

  

懒汉模式实现线程安全的线程池

线程安全的线程池:

#pragma once#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include "Task.hpp"struct ThreadData
{pthread_t tid;std::string name;
};static const int defaultnum=5; //默认线程数量//实现我们的线程池
template<class T>
class ThreadPool
{
public: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);}bool IsQueueEmpty() {return _tasks.empty();}std::string GetThreadName(pthread_t tid){for (const auto &ti : _threads){if (ti.tid == tid)return ti.name;}return "None";}public://注意我们线程调用的函数要求参数和返回值都是void*//但是handler在类中默认有this指针->参数不匹配,可以bind或者声明static或放在类外static void *Handler(/*ThreadPool *this,*/void *args){ThreadPool<T> *tp=static_cast<ThreadPool<T>*>(args);std::string name = tp->GetThreadName(pthread_self());while(true){tp->Lock();while(tp->IsQueueEmpty()) //判断任务是否为空{tp->ThreadSleep(); //条件变量}T t=tp->Pop(); //取出任务tp->Unlock();t(); //处理任务 std::cout<<name<<" run, "<<"result: "<< t.GetResult()<<std::endl;            }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,Handler,this);}}void Push(const T &t) //向任务队列放入任务{Lock();_tasks.push(t); //放入任务Wakeup(); //唤醒线程Unlock();}T Pop() //取出任务{T t=_tasks.front();_tasks.pop();return t;}static ThreadPool<T> *GetInstance() //获取单例对象{if(nullptr==_tp) //创建单例对象后,不会再有申请和释放锁的操作{pthread_mutex_lock(&_lock); //保护临界资源if(_tp==nullptr){std::cout<<"singleton create done"<<std::endl;_tp=new ThreadPool<T>();}pthread_mutex_unlock(&_lock);}return _tp;}private:ThreadPool(const ThreadPool<T>&)=delete;const ThreadPool<T>& operator=(const ThreadPool<T>&)=delete;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:   std::vector<ThreadData> _threads; //线程池std::queue<T> _tasks; //任务队列pthread_mutex_t _mutex; //锁pthread_cond_t _cond; //条件变量static ThreadPool<T> *_tp; //获取单例指针static pthread_mutex_t _lock; //锁
};template<class T>
ThreadPool<T> *ThreadPool<T>::_tp=nullptr;template<class T>
pthread_mutex_t ThreadPool<T>::_lock=PTHREAD_MUTEX_INITIALIZER;

  

运行函数:

#include <iostream>
#include "ThreadPool.hpp"int main()
{//ThreadPool<Task> *tp=new ThreadPool<Task>(5);//tp->Start();sleep(2); //懒汉模式ThreadPool<Task>::GetInstance()->Start();srand(time(nullptr) ^ getpid());    while(true){//1. 构建任务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);ThreadPool<Task>::GetInstance()->Push(t);//2. 交给线程池处理std::cout<<"main thread make task: "<<t.GetTask()<<std::endl;sleep(1);}return 0;
}

  

在这里插入图片描述
  

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

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

  而且对于不同的容器,加锁方式的不同,性能可能也不同(例如hash表的锁表和锁桶)。
因此 STL 默认不是线程安全。如果需要在多线程环境下使用,往往需要调用者自行保证线程安全。

  

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

  对于 unique_ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题。

  对于 shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题。但是标准库实现的时候考虑到了这个问题,基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数。

  

8. 常见锁使用汇总

8.1 互斥锁(Mutex)

  确保在同一时刻只有一个线程能够访问被保护的资源。

  例如,多个线程同时操作一个共享的全局变量时,使用互斥锁来保证数据的一致性。

  

  初始化互斥锁

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

作用:初始化一个互斥锁。

参数:
  mutex:指向要初始化的互斥锁的指针。

  attr:互斥锁的属性指针,通常为 NULL(使用默认属性)。

返回值:成功返回 0,失败返回错误码。

  

  加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

作用:获取互斥锁,如果锁已被占用则阻塞等待。

参数:mutex:要加锁的互斥锁指针。

返回值:成功返回 0,失败返回错误码。

  

  尝试加锁

int pthread_mutex_trylock(pthread_mutex_t *mutex);

作用:尝试获取互斥锁,如果锁可用则获取并返回 0,否则立即返回 EBUSY。

参数:mutex:要尝试加锁的互斥锁指针。

返回值:成功返回 0,锁不可用返回 EBUSY。

  

  解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

作用:释放已获取的互斥锁。

参数:mutex:要解锁的互斥锁指针。

返回值:成功返回 0,失败返回错误码。

  

  销毁互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

作用:销毁指定的互斥锁。

参数:mutex:要销毁的互斥锁指针。

返回值:成功返回 0,失败返回错误码。

  

8.2 条件变量(Condition Variable)

  通常与互斥锁配合使用,用于线程之间的等待和通知。

  比如一个线程需要等待某个条件满足后才能继续执行,而另一个线程在条件满足时通知它。

  

  初始化条件变量

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

作用:初始化一个条件变量。

参数:
  cond:指向要初始化的条件变量的指针。

  attr:条件变量的属性指针,通常为 NULL(使用默认属性)。

返回值:成功返回 0,失败返回错误码。

  

  等待条件变量

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

作用:阻塞当前线程,直到指定的条件变量被唤醒。

参数:
  cond:要等待的条件变量指针。

  mutex:与条件变量关联的互斥锁指针。

返回值:成功返回 0,失败返回错误码。

  

  定时等待条件变量

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

作用:阻塞当前线程,直到指定的条件变量被唤醒或到达指定的超时时间。

参数:
  cond:要等待的条件变量指针。

  mutex:与条件变量关联的互斥锁指针。

  abstime:指定的超时时间。

返回值:成功返回 0,超时返回 ETIMEDOUT,失败返回其他错误码。

  

  唤醒一个等待条件变量的线程

int pthread_cond_signal(pthread_cond_t *cond);

作用:唤醒至少一个等待指定条件变量的线程。

参数:cond:要唤醒的条件变量指针。

返回值:成功返回 0,失败返回错误码。

  

  唤醒所有等待条件变量的线程

int pthread_cond_broadcast(pthread_cond_t *cond);

作用:唤醒所有等待指定条件变量的线程。

参数:cond:要唤醒的条件变量指针。

返回值:成功返回 0,失败返回错误码。

  

  销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

作用:销毁指定的条件变量。

参数:cond:要销毁的条件变量指针。

返回值:成功返回 0,失败返回错误码。

  

8.3 信号量(Semaphore)

  用于控制同时访问某一资源的线程数量。

  例如限制同时访问数据库连接的线程数量。

  

  初始化信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);

作用:初始化一个信号量。

参数:
  sem:指向要初始化的信号量的指针。

  pshared:表示信号量的共享属性,0 表示线程间共享,非 0 表示进程间共享。

  value:信号量的初始值。

返回值:成功返回 0,失败返回 -1。

  

  等待信号量

int sem_wait(sem_t *sem);

作用:等待信号量的值大于 0,然后将其减 1。

参数:sem:要操作的信号量指针。

返回值:成功返回 0,失败返回 -1。

  

  尝试等待信号量

int sem_trywait(sem_t *sem);

作用:尝试等待信号量,如果信号量的值大于 0,则将其减 1 并立即返回;否则返回错误。

参数:sem:要操作的信号量指针。

返回值:成功返回 0,信号量不可用返回 -1 并设置 errno 为 EAGAIN。

  

  释放信号量

int sem_post(sem_t *sem);

作用:将信号量的值增加 1。

参数:sem:要操作的信号量指针。

返回值:成功返回 0,失败返回 -1。

  

  获取信号量的值

int sem_getvalue(sem_t *sem, int *sval);

作用:获取信号量的当前值,并将其存储在 sval 指向的变量中。

参数:
  sem:要操作的信号量指针。

  sval:用于存储信号量值的整数指针。

返回值:成功返回 0,失败返回 -1。

  

  销毁信号量

int sem_destroy(sem_t *sem);

作用:销毁指定的信号量。

参数:sem:要销毁的信号量指针。

返回值:成功返回 0,失败返回 -1。

  

8.4 自旋锁(Spin Lock)

  线程在获取锁失败时,会一直循环尝试获取,而不是阻塞等待。

  适用于锁被持有的时间很短的情况,能避免线程切换的开销,但如果锁被长时间持有,会浪费 CPU 资源。

  

  初始化自旋锁

int spinlock_init(spinlock_t *lock, const spinlockattr_t *attr);

作用:初始化指定的自旋锁。

参数:
  lock:指向要初始化的自旋锁的指针。
  attr:自旋锁属性指针,可为 NULL(使用默认属性)。

返回值:成功返回 0,失败返回错误码。

  

  销毁自旋锁

int spinlock_destroy(spinlock_t *lock);

作用:销毁指定的自旋锁。

参数:lock:要销毁的自旋锁指针。

返回值:成功返回 0,失败返回错误码。

  

  尝试获取自旋锁(读)

int spinlock_rdlock(spinlock_t *lock);

作用:尝试获取自旋锁的读锁。

参数:lock:指向要获取读锁的自旋锁的指针。

返回值:成功返回 0,失败返回错误码。

  

  尝试获取自旋锁(写)

int spinlock_wrlock(spinlock_t *lock);

作用:尝试获取自旋锁的写锁。

参数:lock:指向要获取写锁的自旋锁的指针。

返回值:成功返回 0,失败返回错误码。

  

  释放自旋锁

int spinlock_unlock(spinlock_t *lock);

作用:释放指定的自旋锁。

参数:lock:要释放的自旋锁指针。

返回值:成功返回 0,失败返回错误码。

  

8.5 读写锁(Read-Write Lock)

  区分读操作和写操作。允许多个线程同时进行读操作,但在写操作时,不允许其他线程进行读或写操作。

  适用于读操作频繁而写操作较少的场景,比如共享数据的读取次数远多于修改次数的情况。

在这里插入图片描述  

  初始化读写锁

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

作用:初始化指定的读写锁。

参数:
  rwlock:指向要初始化的读写锁的指针。
  attr:读写锁属性指针,可为 NULL(使用默认属性)。

返回值:成功返回 0,失败返回错误码。

  

  销毁读写锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

作用:销毁指定的读写锁。

参数:rwlock:要销毁的读写锁指针。

返回值:成功返回 0,失败返回错误码。

  

  获取读写锁的读锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

作用:尝试获取指定读写锁的读锁。

参数:rwlock:指向要获取读锁的读写锁的指针。

返回值:成功返回 0,失败返回错误码。

  

  获取读写锁的写锁

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

作用:尝试获取指定读写锁的写锁。

参数:rwlock:指向要获取写锁的读写锁的指针。

返回值:成功返回 0,失败返回错误码。

  

  释放读写锁

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

作用:释放指定的读写锁。

参数:rwlock:要释放的读写锁指针。

返回值:成功返回 0,失败返回错误码。

  

  设置读写锁的优先级

int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);

作用:设置读写锁的优先级。

参数:
  attr:读写锁属性指针。
  pref:优先级选择,有以下 3 种:

  PTHREAD_RWLOCK_PREFER_READER_NP (默认设置)读者优先,可能会导致写者饥饿情况。

  PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和 PTHREAD_RWLOCK_PREFER_READER_NP 一致。

  PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁。

返回值:成功返回 0,失败返回错误码。

  

其他概念:

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

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

  公平锁:公平锁按照线程请求锁的先后顺序来分配锁。先请求的线程先获取,保证了顺序公平。保证顺序,适合要求严格公平的场景。但性能开销大,高并发时吞吐量可能受影响。

  非公平锁:非公平锁不按请求顺序分配锁,锁释放时竞争的线程都可能获取,不一定是先请求的。性能好,高并发时吞吐量可能高。但可能导致线程饥饿,行为不太确定。

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

            

相关文章:

【Linux】线程——线程池、线程池的实现、线程安全的线程池、单例模式的概念、饿汉和懒汉模式、互斥锁、条件变量、信号量、自旋锁、读写锁

文章目录 Linux线程7. 线程池7.1 线程池介绍7.2 线程池的实现7.3 线程安全的线程池7.3.1 单例模式的概念7.3.2 饿汉和懒汉模式 8. 常见锁使用汇总8.1 互斥锁&#xff08;Mutex&#xff09;8.2 条件变量&#xff08;Condition Variable&#xff09;8.3 信号量&#xff08;Semaph…...

stm32入门-----TIM定时器(PWM输出比较——下)

目录 前言 一、硬件元器件介绍 1.舵机 2.直流电机驱动 二、C语言编程步骤 1.开启时钟 2.配置输出的GPIO口 3.配置时基单元 4.初始化输出比较通道 5.开启定时器 三、实践项目 1.PWM驱动LED呼吸灯 2.PWM驱动舵机 3.PWM驱动直流电机 前言 本期我们就开始去进行TIM定时…...

css实现线条中间高亮,左右两边模糊(linear-gradient的运用)

效果&#xff1a; <div class"line"></div> .line {height: 1px;background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, #a9c2ff 50%, rgba(255, 255, 255, 0) 100%);border-radius: 4px 4px 4px 4px; } CSS实现边框底部渐变色的方法:(最简单…...

【数据结构】建堆算法复杂度分析及TOP-K问题

【数据结构】建堆算法复杂度分析及TOP-K问题 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;数据结构 文章目录 【数据结构】建堆算法复杂度分析及TOP-K问题前言一.复杂度分析1.1向下建堆复杂度1.2向上建堆复杂度1.3堆排序复杂度 二.TOP-K问…...

Thinkphp5实现前后端通过接口通讯基本操作方法

在ThinkPHP5框架中&#xff0c;实现前后端通过接口通讯是一个常见的需求&#xff0c;尤其是在开发RESTful API时。下面是一个基本的步骤指南&#xff0c;用于设置ThinkPHP5来创建API接口&#xff0c;并使前端能够通过HTTP请求与后端进行通讯。 1. 创建API模块 首先&#xff0…...

Go 语言任务编排 WaitGroup

WaitGroup 是常用的 Go 同步原语之一,用来做任务编排。它要解决的就是并发-等待的问题: 现在有一个 goroutine A 在检查点 ( checkpoint ) 等待一组 goroutine 全部完成它们的任务,如果这些 goroutine 还没全部完成任务,那么 goroutine A 就会被阻塞在检查点,直到所有的 …...

星环科技推出知识库产品 AI PC时代数据交互方式变革

随着企业业务的快速发展&#xff0c;数据量呈爆炸式增长&#xff0c;有效的知识管理成为企业面临的重要问题。企业遇到的普遍问题是大量的结构化、半结构化数据存储在不同的系统中&#xff0c;需要用多种计算机语言进行检索。而大模型彻底改变了人们和数据的交互方式&#xff0…...

10道JVM经典面试题

1、 JVM中&#xff0c;new出来的对象是在哪个区&#xff1f; 2、 说说类加载有哪些步骤&#xff1f; 3、 JMM是什么&#xff1f; 4、 说说JVM内存结构&#xff1f; 5、 MinorGC和FullGC有什么区别&#xff1f; 6、 什么是STW? 7、 什么情况下会发生堆/栈溢出&#xff1f…...

Redisson常用的数据结构及应用场景

Redisson 提供了一系列高级数据结构&#xff0c;这些数据结构封装了 Redis 的原生数据类型&#xff0c;提供了 Java API 的便利性和分布式特性。以下是 Redisson 中一些常用的数据结构&#xff0c;场景还在不断完善中&#xff1a; RBucket&#xff1a;这是一个简单的键值对存储…...

【实现100个unity特效之8】使用ShaderGraph实现2d贴图中指定部分局部发光效果

最终效果 寒冰法师 火焰法师 文章目录 最终效果寒冰法师火焰法师 素材一、功能分析实现方法基本思路Unity的Bloom后处理为什么关键部位白色&#xff1f;最终结果 二、 新建URP项目三、合并图片四、使用PS制作黑白图片方法一 手动涂鸦方法二 魔棒工具1. 拖入图片进PS&#xff0…...

Ubuntu 24.04 LTS Noble安装Docker Desktop简单教程

Docker 为用户提供了在 Ubuntu Linux 上快速创建虚拟容器的能力。但是&#xff0c;那些不想使用命令行管理容器的人可以在 Ubuntu 24.04 LTS 上安装 Docker Desktop GUI&#xff0c;本教程将提供用于设置 Docker 图形用户界面的命令…… Docker Desktop 是一个易于使用的集成容…...

XML 和 SimpleXML 入门教程

XML 和 SimpleXML 入门教程 XML&#xff08;可扩展标记语言&#xff09;是一种用于存储和传输数据的标记语言。它是一种自我描述的语言&#xff0c;允许用户定义自己的标签来表示数据。SimpleXML 是 PHP 中的一个扩展&#xff0c;用于解析和操作 XML 数据。本文将介绍 XML 和 …...

leetcode--链表类题目总结

本文作为刷题时对链表类题目的总结. 常见技巧: 引入虚拟头节点 便于处理边界情况便于对链表操作快慢双指针(判环,找环的入口等)链表逆序(推荐使用 虚拟头节点 头插法 进行逆序) 链表逆序( 头插法 虚拟头节点):链表内指定区间反转_牛客题霸_牛客网 虚拟节点:合并…...

打卡第22天------回溯算法

开始学习了,希望我可以尽快成功上岸! 一、回溯理论基础 什么是回溯法?回溯法也可以叫做回溯搜索法,它是一种搜索的方式。 回溯是递归的副产品,只要有递归就会有回溯。 回溯法的效率回溯法的本质是穷举,穷举所有可能,然后找出我们想要的答案。如果想让回溯法高效一些,可…...

Ubuntu对比两个文件内容有什么区别?

在Ubuntu&#xff08;或任何基于Linux的系统&#xff09;中&#xff0c;你可以使用多种命令行工具来比较两个文件的内容差异。以下是一些常用的方法&#xff1a; 1. **diff 命令**&#xff1a; diff 是Linux中用于比较两个文件差异的标准工具。它逐行比较文件&#xff0c;并显示…...

python:本机摄像头目标检测实时推理(使用YOLOv8n模型)

本文将介绍如何使用本机摄像头进行目标检测实时推理的python代码。 文章目录 一、下载YOLO权重文件二、环境配置三、完整代码 一、下载YOLO权重文件 https://github.com/ultralytics/ultralytics?tabreadme-ov-file 拉到网页最下面&#xff0c;选择适合的模型&#xff0c;下…...

Spark实时(四):Strctured Streaming简单应用

文章目录 Strctured Streaming简单应用 一、Output Modes输出模式 二、Streaming Table API 三、​​​​​​​​​​​​​​Triggers 1、​​​​​​​unspecified&#xff08;默认模式&#xff09; 2、​​​​​​​​​​​​​​Fixed interval micro-batches&am…...

SpringBoot上传超大文件导致OOM,完美问题解决办法

问题描述 报错: Caused by: java.lang.OutOfMemoryError at java.io.ByteArrayOutputStream.hugeCapacity(ByteArrayOutputStream.java:123) ~[?:1.8.0_381] at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:117) ~[?:1.8.0_381] at java.…...

PyTorch 的各个核心模块和它们的功能

1. torch 核心功能 张量操作&#xff1a;PyTorch 的张量是一个多维数组&#xff0c;类似于 NumPy 的 ndarray&#xff0c;但支持 GPU 加速。数学运算&#xff1a;提供了各种数学运算&#xff0c;包括线性代数操作、随机数生成等。自动微分&#xff1a;torch.autograd 模块用于…...

Java开发之LinkedList源码分析

#来自ゾフィー&#xff08;佐菲&#xff09; 1 简介 LinkedList 的底层数据结构是双向链表。可以当作链表、栈、队列、双端队列来使用。有以下特点&#xff1a; 在插入或删除数据时&#xff0c;性能好&#xff1b;允许有 null 值&#xff1b;查询效率不高&#xff1b;线程不安…...

外卖霸王餐系统架构怎么选?

在当今日益繁荣的外卖市场中&#xff0c;外卖霸王餐作为一种独特的营销策略&#xff0c;受到了众多商家的青睐。然而&#xff0c;要想成功实施外卖霸王餐活动&#xff0c;一个安全、稳定且高效的架构选择至关重要。本文将深入探讨外卖霸王餐架构的选择&#xff0c;以期为商家提…...

AV1技术学习:Transform Coding

对预测残差进行变换编码&#xff0c;去除潜在的空间相关性。VP9 采用统一的变换块大小设计&#xff0c;编码块中的所有的块共享相同的变换大小。VP9 支持 4 4、8 8、16 16、32 32 四种正方形变换大小。根据预测模式选择由一维离散余弦变换 (DCT) 和非对称离散正弦变换 (ADS…...

Git操作指令

Git操作指令 一、安装git 1、设置配置信息&#xff1a; # global全局配置 git config --global user.name "Your username" git config --global user.email "Your email"2、查看git版本号 git -v # or git --version3、查看配置信息&#xff1a; git…...

CSS 创建:从入门到精通

CSS 创建:从入门到精通 CSS(层叠样式表)是网页设计中不可或缺的一部分,它用于控制网页的布局和样式。本文将详细介绍CSS的创建过程,包括基本概念、语法结构、选择器、样式属性以及如何将CSS应用到HTML中。无论您是初学者还是有经验的开发者,本文都将为您提供宝贵的信息。…...

Windows 11 系统对磁盘进行分区保姆级教程

Windows 11磁盘分区 磁盘分区是将硬盘驱动器划分为多个逻辑部分的过程&#xff0c;每个逻辑部分都可以独立使用和管理。在Windows 11操作系统中进行磁盘分区主要有以下几个作用和意义&#xff1a; 组织和管理数据&#xff1a;分区可以帮助用户更好地组织他们的数据&#xff0c…...

探索WebKit的CSS盒模型:深入理解Web布局的基石

探索WebKit的CSS盒模型&#xff1a;深入理解Web布局的基石 在Web开发的世界中&#xff0c;CSS盒模型&#xff08;Box Model&#xff09;是构建网页布局的核心原理。WebKit&#xff0c;作为Safari浏览器的渲染引擎&#xff0c;对CSS盒模型有着深入而精确的支持。本文将带你深入…...

c++初阶知识——string类详解

目录 前言&#xff1a; 1.标准库中的string类 1.1 auto和范围for auto 范围for 1.2 string类常用接口说明 1.string类对象的常见构造 1.3 string类对象的访问及遍历操作 1.4. string类对象的修改操作 1.5 string类非成员函数 2.string类的模拟实现 2.1 经典的string…...

php接口返回的json字符串,json_decode()失败,原来是多了红点

问题&#xff1a; 调用某个接口返回的json&#xff0c;json_decode()失败&#xff0c;返回数据为null&#xff0c; echo json_last_error();返回错误码 4 经过多次调试发现&#xff1a;多出来一个红点&#xff0c;预览是看不到的。 解决&#xff1a;要去除BOM头部 $resul…...

Python3网络爬虫开发实战(2)爬虫基础库

文章目录 一、urllib1. urlparse 实现 URL 的识别和分段2. urlunparse 用于构造 URL3. urljoin 用于两个链接的拼接4. urlencode 将 params 字典序列化为 params 字符串5. parse_qs 和 parse_qsl 用于将 params 字符串反序列化为 params 字典或列表6. quote 和 unquote 对 URL的…...

el-image预览图片点击遮盖处关闭预览

预览关闭按钮不明显 解决方式&#xff1a; 1.修改按钮样式明显点&#xff1a; //el-image 添加自定义类名&#xff0c;下文【test-image】代指 .test-image .el-icon-circle-close{ color:#fff; font-size:20px; ...改成很明显的样式 }2.使用事件监听&#xff0c;监听当前遮…...

基于Neo4j将知识图谱用于检索增强生成:Knowledge Graphs for RAG

Knowledge Graphs for RAG 本文是学习https://www.deeplearning.ai/short-courses/knowledge-graphs-rag/这门课的学习笔记。 What you’ll learn in this course Knowledge graphs are used in development to structure complex data relationships, drive intelligent sea…...

康康近期的慢SQL(oracle vs 达梦)

近期执行的sql&#xff0c;哪些比较慢&#xff1f; 或者健康检查时搂一眼状态 oracle&#xff1a; --最近3天内的慢sql set lines 200 pages 100 col txt for a65 col sql_id for a13 select a.sql_id,a.cnt,a.pctload,b.sql_text txt from (select * from (select sql_id,co…...

探索 GPT-4o mini:成本效益与创新的双重驱动

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

2.6基本算法之动态规划2989:糖果

描述 由于在维护世界和平的事务中做出巨大贡献&#xff0c;Dzx被赠予糖果公司2010年5月23日当天无限量糖果免费优惠券。在这一天&#xff0c;Dzx可以从糖果公司的N件产品中任意选择若干件带回家享用。糖果公司的N件产品每件都包含数量不同的糖果。Dzx希望他选择的产品包含的糖…...

12.顶部带三角形的边框 CSS 关键字 currentColor

顶部带三角形的边框 创建一个在顶部带有三角形的内容容器。 使用 ::before 和 ::after 伪元素创建两个三角形。两个三角形的颜色应分别与容器的 border-color 和容器的 background-color 相同。一个三角形(::before)的 border-width 应比另一个(::after)宽 1px,以起到边框的作…...

Llama中模块参数大小

LLama2中&#xff0c;流程中数据大小的变换如下 Transformer模块 第一次输入&#xff0c;进行prefill&#xff0c;输入x维度为[1, 8, 4096] 1. 构建wq,wk,wv,wo,尺寸均为[4096,4096]&#xff0c; 与x点乘&#xff0c;得到xq, xk, xv 2. 构建KV cache&#xff0c; 尺寸为 [b…...

Modbus转EtherCAT网关将Modbus协议的数据格式转换为EtherCAT协议

随着工业自动化技术的快速发展&#xff0c;不同通信协议之间的互操作性变得越来越重要。Modbus作为一种广泛使用的串行通信协议&#xff0c;与以太网为基础的EtherCAT协议之间的转换需求日益增长。本文将从网关功能、硬件设计、性能以及应用案例来介绍这款Modbus转EtherCAT网关…...

【开发实战】QT5 + OpenCV4 开发环境配置应用演示

前言 作为深度学习算法工程师&#xff0c;必须要掌握应用开发技能吗&#xff1f;搞工程肯定是必须要会界面开发&#xff0c;QT就是一个很不错的选择。本文以QT5.15 OpenCV4.8 OpenVINO2023为例&#xff0c;搭建应用开发环境&#xff0c;演示深度学习模型的QT应用案例。 开发…...

“微软蓝屏”事件暴露的网络安全问题及应对策略

“微软蓝屏”事件暴露了网络安全哪些问题&#xff1f; 近日&#xff0c;一次由微软视窗系统软件更新引发的全球性“微软蓝屏”事件&#xff0c;不仅成为科技领域的热点新闻&#xff0c;更是一次对全球IT基础设施韧性与安全性的深刻检验。这次事件&#xff0c;源于美国电脑安全技…...

白骑士的PyCharm教学基础篇 1.3 调试与运行

系列目录 上一篇&#xff1a;白骑士的PyCharm教学基础篇 1.2 PyCharm基本操作 配置与调试环境 配置调试环境 选择解释器 在 PyCharm 中选择正确的 Python 解释器&#xff1a;依次点击 “File” -> “Settings” -> “Project: [项目名]” -> “Project Interpret…...

爬虫学习1:初学者简单了解爬虫的基本认识和操作(详细参考图片)

爬虫 定义&#xff1a;爬虫&#xff08;Web Crawler 或 Spider&#xff09;是一种自动访问互联网上网页的程序&#xff0c;其主要目的是索引网页内容&#xff0c;以便搜索引擎能够快速检索到相关信息。以下是爬虫的一些关键特性和功能&#xff1a; 自动化访问&#xff1a;爬虫能…...

WHAT - 通过 shadcn 组件源码学习 React

目录 一、button1. 导入部分2. 定义按钮的样式变体1. variant2. size总结 3. 定义按钮的属性类型4. 定义按钮组件5. 导出组件和样式变体总结 二、multi-select多选组件的核心上下文与状态1. 上下文与钩子2. MultiSelector 组件 组件子部分1. MultiSelectorTrigger2. MultiSelec…...

grafana对接zabbix数据展示

目录 1、初始化、安装grafana 2、浏览器访问 3、安装zabbix 4、zabbix数据对接grafana 5、如何导入模板&#xff1f; ① 设置键值 ② 在zabbix web端完成自定义监控项 ③ garafana里添加nginx上面的的三个监控项 6、如何自定义监控项&#xff1f; 以下实验沿用上一篇z…...

C++ 学习补充 1:短链算法

短链算法 短链算法&#xff1a; 将长链接 转化为 一个短key 之所以不是短url 是因为 &#xff0c;url 短链不区分大小写&#xff0c;可用空间比较小。 短链算法通常用于将一个长网址转换成一个较短的字符串&#xff0c;以便于分享和存储。这种算法通常需要满足以下条件&#…...

硅纪元视角 | 语音克隆突破:微软VALL-E 2,Deepfake新纪元!

在数字化浪潮的推动下&#xff0c;人工智能&#xff08;AI&#xff09;正成为塑造未来的关键力量。硅纪元视角栏目紧跟AI科技的最新发展&#xff0c;捕捉行业动态&#xff1b;提供深入的新闻解读&#xff0c;助您洞悉技术背后的逻辑&#xff1b;汇聚行业专家的见解&#xff0c;…...

没有51基础,能不能学好STM32?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「STM32的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 我们通常准备攻读一本大部…...

Web开发:VUE3小白开发入门基础笔记

一、基本语法 1.click 后端路由&#xff1a;api/GetDataList 返回值&#xff1a;Value 前端要做的事&#xff1a; ①拿到Value值&#xff0c;传到a标签 ②a标签有一个按钮&#xff0c;每点击一下&#xff0c;Value的值加一。 前端需要用click语法 【代码】 <template>…...

技术周总结 2024.07.15~07.21周日(Spark性能优化)

文章目录 一、07.19 周五1.1&#xff09;问题01&#xff1a; spark性能优化1.2&#xff09;问题02&#xff1a; spark是怎么应用在机器学习领域的1.3&#xff09;问题03&#xff1a;spark自带工具有哪些&#xff1f;1.4&#xff09;问题04&#xff1a; spark日志的知识点有哪些…...

提高性能的常见技术

1.数据库层面&#xff1a; 读写分离&#xff0c;对于大部分业务来说&#xff0c;读取操作要大于写入&#xff0c;同一个库&#xff0c;既读又写的话&#xff0c;负载会比较重&#xff0c;拆分为读库和写入库&#xff0c;可以降低数据库的负载&#xff0c;分时或延迟将写入的数…...

LeetCode206 反转链表

前言 题目&#xff1a; 206. 反转链表 文档&#xff1a; 代码随想录——反转链表 编程语言&#xff1a; C 解题状态&#xff1a; 有了思路以后没敢尝试 思路 需要注意的是创建指针不会申请额外的内存空间。 代码 方法一&#xff1a; 双指针法/迭代 我的理解是创建了三个指针…...