建网站怎么做报分系统,app开发公司 无冬,安宁网站建设 熊掌,建设中网站首页文章目录 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… 文章目录 Linux线程7. 线程池7.1 线程池介绍7.2 线程池的实现7.3 线程安全的线程池7.3.1 单例模式的概念7.3.2 饿汉和懒汉模式 8. 常见锁使用汇总8.1 互斥锁Mutex8.2 条件变量Condition Variable8.3 信号量Semaphore8.4 自旋锁Spin Lock8.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 stringstd::string opers-*/%;enum{DivZero1,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(_data20) _exitcodeDivZero;else _result_data1/_data2;}break;case %:{if(_data20) _exitcodeModZero;else _result_data1%_data2;}break;default:_exitcodeUnknown;break;}}//Task对象重载运算符(),()直接进行run函数void operator()(){run();}std::string GetResult(){std::string rstd::to_string(_data1);r_oper;rstd::to_string(_data2);r;rstd::to_string(_result);r[code: ;rstd::to_string(_exitcode);r];return r;}std::string GetTask(){std::string rstd::to_string(_data1);r_oper;rstd::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.hppstruct ThreadData
{pthread_t tid;std::string name;
};static const int defaultnum5; //默认线程数量//实现我们的线程池
templateclass 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){ThreadPoolT *tpstatic_castThreadPoolT*(args);while(true){tp-Lock();while(tp-IsQueueEmpty()) //判断任务是否为空{tp-ThreadSleep(); //条件变量}T ttp-Pop(); //取出任务tp-Unlock();t(); //处理任务 std::cout run, result: t.GetResult()std::endl; }return nullptr;}void Start() //启动线程池{int num_threads.size();for(int i0;inum;i){_threads[i].namethread-std::to_string(i1);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 numdefaultnum):_threads(num){pthread_mutex_init(_mutex,nullptr);pthread_cond_init(_cond,nullptr);} ~ThreadPool(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_cond);}private: std::vectorThreadData _threads; //线程池std::queueT _tasks; //任务队列pthread_mutex_t _mutex; //锁pthread_cond_t _cond; //条件变量
};运行函数
#include iostream
#include ThreadPool.hppint main()
{ThreadPoolTask *tpnew ThreadPoolTask(5);tp-Start();srand(time(nullptr) ^ getpid()); while(true){//1. 构建任务int xrand()%101;usleep(10);int yrand()%5;char opopers[rand()%opers.size()];Task t(x,y,op);tp-Push(t);//2. 交给线程池处理std::coutmain 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.hppstruct ThreadData
{pthread_t tid;std::string name;
};static const int defaultnum5; //默认线程数量//实现我们的线程池
templateclass 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){ThreadPoolT *tpstatic_castThreadPoolT*(args);std::string name tp-GetThreadName(pthread_self());while(true){tp-Lock();while(tp-IsQueueEmpty()) //判断任务是否为空{tp-ThreadSleep(); //条件变量}T ttp-Pop(); //取出任务tp-Unlock();t(); //处理任务 std::coutname run, result: t.GetResult()std::endl; }return nullptr;}void Start() //启动线程池{int num_threads.size();for(int i0;inum;i){_threads[i].namethread-std::to_string(i1);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 ThreadPoolT *GetInstance() //获取单例对象{if(nullptr_tp) //创建单例对象后不会再有申请和释放锁的操作{pthread_mutex_lock(_lock); //保护临界资源if(_tpnullptr){std::coutsingleton create donestd::endl;_tpnew ThreadPoolT();}pthread_mutex_unlock(_lock);}return _tp;}private:ThreadPool(const ThreadPoolT)delete;const ThreadPoolT operator(const ThreadPoolT)delete;ThreadPool(int numdefaultnum):_threads(num){pthread_mutex_init(_mutex,nullptr);pthread_cond_init(_cond,nullptr);} ~ThreadPool(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_cond);}private: std::vectorThreadData _threads; //线程池std::queueT _tasks; //任务队列pthread_mutex_t _mutex; //锁pthread_cond_t _cond; //条件变量static ThreadPoolT *_tp; //获取单例指针static pthread_mutex_t _lock; //锁
};templateclass T
ThreadPoolT *ThreadPoolT::_tpnullptr;templateclass T
pthread_mutex_t ThreadPoolT::_lockPTHREAD_MUTEX_INITIALIZER;运行函数
#include iostream
#include ThreadPool.hppint main()
{//ThreadPoolTask *tpnew ThreadPoolTask(5);//tp-Start();sleep(2); //懒汉模式ThreadPoolTask::GetInstance()-Start();srand(time(nullptr) ^ getpid()); while(true){//1. 构建任务int xrand()%101;usleep(10);int yrand()%5;char opopers[rand()%opers.size()];Task t(x,y,op);//tp-Push(t);ThreadPoolTask::GetInstance()-Push(t);//2. 交给线程池处理std::coutmain 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操作当需要更新数据时判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败失败则重试一般是一个自旋的过程即不断重试。