【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 互斥锁(Mutex)8.2 条件变量(Condition Variable)8.3 信号量(Semaph…...

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

css实现线条中间高亮,左右两边模糊(linear-gradient的运用)
效果: <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问题 🔥个人主页:大白的编程日记 🔥专栏:数据结构 文章目录 【数据结构】建堆算法复杂度分析及TOP-K问题前言一.复杂度分析1.1向下建堆复杂度1.2向上建堆复杂度1.3堆排序复杂度 二.TOP-K问…...
Thinkphp5实现前后端通过接口通讯基本操作方法
在ThinkPHP5框架中,实现前后端通过接口通讯是一个常见的需求,尤其是在开发RESTful API时。下面是一个基本的步骤指南,用于设置ThinkPHP5来创建API接口,并使前端能够通过HTTP请求与后端进行通讯。 1. 创建API模块 首先࿰…...
Go 语言任务编排 WaitGroup
WaitGroup 是常用的 Go 同步原语之一,用来做任务编排。它要解决的就是并发-等待的问题: 现在有一个 goroutine A 在检查点 ( checkpoint ) 等待一组 goroutine 全部完成它们的任务,如果这些 goroutine 还没全部完成任务,那么 goroutine A 就会被阻塞在检查点,直到所有的 …...
星环科技推出知识库产品 AI PC时代数据交互方式变革
随着企业业务的快速发展,数据量呈爆炸式增长,有效的知识管理成为企业面临的重要问题。企业遇到的普遍问题是大量的结构化、半结构化数据存储在不同的系统中,需要用多种计算机语言进行检索。而大模型彻底改变了人们和数据的交互方式࿰…...

10道JVM经典面试题
1、 JVM中,new出来的对象是在哪个区? 2、 说说类加载有哪些步骤? 3、 JMM是什么? 4、 说说JVM内存结构? 5、 MinorGC和FullGC有什么区别? 6、 什么是STW? 7、 什么情况下会发生堆/栈溢出?…...
Redisson常用的数据结构及应用场景
Redisson 提供了一系列高级数据结构,这些数据结构封装了 Redis 的原生数据类型,提供了 Java API 的便利性和分布式特性。以下是 Redisson 中一些常用的数据结构,场景还在不断完善中: RBucket:这是一个简单的键值对存储…...

【实现100个unity特效之8】使用ShaderGraph实现2d贴图中指定部分局部发光效果
最终效果 寒冰法师 火焰法师 文章目录 最终效果寒冰法师火焰法师 素材一、功能分析实现方法基本思路Unity的Bloom后处理为什么关键部位白色?最终结果 二、 新建URP项目三、合并图片四、使用PS制作黑白图片方法一 手动涂鸦方法二 魔棒工具1. 拖入图片进PS࿰…...

Ubuntu 24.04 LTS Noble安装Docker Desktop简单教程
Docker 为用户提供了在 Ubuntu Linux 上快速创建虚拟容器的能力。但是,那些不想使用命令行管理容器的人可以在 Ubuntu 24.04 LTS 上安装 Docker Desktop GUI,本教程将提供用于设置 Docker 图形用户界面的命令…… Docker Desktop 是一个易于使用的集成容…...
XML 和 SimpleXML 入门教程
XML 和 SimpleXML 入门教程 XML(可扩展标记语言)是一种用于存储和传输数据的标记语言。它是一种自我描述的语言,允许用户定义自己的标签来表示数据。SimpleXML 是 PHP 中的一个扩展,用于解析和操作 XML 数据。本文将介绍 XML 和 …...
leetcode--链表类题目总结
本文作为刷题时对链表类题目的总结. 常见技巧: 引入虚拟头节点 便于处理边界情况便于对链表操作快慢双指针(判环,找环的入口等)链表逆序(推荐使用 虚拟头节点 头插法 进行逆序) 链表逆序( 头插法 虚拟头节点):链表内指定区间反转_牛客题霸_牛客网 虚拟节点:合并…...

打卡第22天------回溯算法
开始学习了,希望我可以尽快成功上岸! 一、回溯理论基础 什么是回溯法?回溯法也可以叫做回溯搜索法,它是一种搜索的方式。 回溯是递归的副产品,只要有递归就会有回溯。 回溯法的效率回溯法的本质是穷举,穷举所有可能,然后找出我们想要的答案。如果想让回溯法高效一些,可…...
Ubuntu对比两个文件内容有什么区别?
在Ubuntu(或任何基于Linux的系统)中,你可以使用多种命令行工具来比较两个文件的内容差异。以下是一些常用的方法: 1. **diff 命令**: diff 是Linux中用于比较两个文件差异的标准工具。它逐行比较文件,并显示…...

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

Spark实时(四):Strctured Streaming简单应用
文章目录 Strctured Streaming简单应用 一、Output Modes输出模式 二、Streaming Table API 三、Triggers 1、unspecified(默认模式) 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 核心功能 张量操作:PyTorch 的张量是一个多维数组,类似于 NumPy 的 ndarray,但支持 GPU 加速。数学运算:提供了各种数学运算,包括线性代数操作、随机数生成等。自动微分:torch.autograd 模块用于…...
Java开发之LinkedList源码分析
#来自ゾフィー(佐菲) 1 简介 LinkedList 的底层数据结构是双向链表。可以当作链表、栈、队列、双端队列来使用。有以下特点: 在插入或删除数据时,性能好;允许有 null 值;查询效率不高;线程不安…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...

ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...