网站开发外包费用会计科目,个人建网站,拓者设计吧电脑版,外贸营销文案目录 一、线程池
1、线程池
2、线程池代码
3、线程池的应用场景
二、单例模式的线程安全问题
1、线程池的单例模式
2、线程安全问题
三、其他锁 一、线程池
1、线程池
线程池是一种线程使用模式。线程池里面可以维护一些线程。
为什么要有线程池#xff1f;
因为在…目录 一、线程池
1、线程池
2、线程池代码
3、线程池的应用场景
二、单例模式的线程安全问题
1、线程池的单例模式
2、线程安全问题
三、其他锁 一、线程池
1、线程池
线程池是一种线程使用模式。线程池里面可以维护一些线程。
为什么要有线程池
因为在我们使用线程去处理各种任务的时候尤其是一些执行时间短的任务我们必须要先对线程进行创建然后再进行任务处理最后再销毁线程效率是比较低的。而且有的时候线程过多会带来调度开销进而影响缓存局部性和整体性能。
于是我们可以通过线程池预先创建出一批线程线程池维护着这些线程线程等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。
线程池不仅能够保证内核的充分利用还能防止过分调度。
2、线程池代码
我们先对线程进行封装Thread.hpp
#pragma once
#include iostream
#include string
#include cstdio
#include pthread.husing namespace std;
typedef void *(*fun_t)(void *);class ThreadData
{
public:void *arg_;string name_;
};class Thread
{
public:Thread(int num, fun_t callback, void *arg): func_(callback){char buffer[64];snprintf(buffer, sizeof(buffer), Thread-%d, num);name_ buffer;tdata_.name_ name_;tdata_.arg_ arg;}void start(){pthread_create(tid_, nullptr, func_, (void *)tdata_);}void join(){pthread_join(tid_, nullptr);}string name(){return name_;}~Thread(){}private:pthread_t tid_;string name_;fun_t func_;ThreadData tdata_;
};
线程池代码threadPool.hpp
#pragma once
#include vector
#include queue
#include thread.hpp#define THREAD_NUM 3template class T
class ThreadPool
{
public:bool Empty(){return task_queue_.empty();}pthread_mutex_t *getmutex(){return lock;}void wait(){pthread_cond_wait(cond, lock);}T gettask(){T t task_queue_.front();task_queue_.pop();return t;}public:ThreadPool(int num THREAD_NUM) : num_(num){for (int i 0; i num_; i){threads_.push_back(new Thread(i, routine, this));}pthread_mutex_init(lock, nullptr);pthread_cond_init(cond, nullptr);}static void *routine(void *arg){ThreadData *td (ThreadData *)arg;ThreadPoolT *tp (ThreadPoolT *)td-arg_;while (true){T task;{pthread_mutex_lock(tp-getmutex());while (tp-Empty())tp-wait();task tp-gettask();pthread_mutex_unlock(tp-getmutex());}cout xy task() pthread_self() endl;}}void run(){for (auto iter : threads_){iter-start();}}void PushTask(const T task){pthread_mutex_lock(lock);task_queue_.push(task);pthread_mutex_unlock(lock);pthread_cond_signal(cond);}~ThreadPool(){for (auto iter : threads_){iter-join();delete iter;}pthread_mutex_destroy(lock);pthread_cond_destroy(cond);}private:vectorThread * threads_;int num_;queueT task_queue_;pthread_mutex_t lock;pthread_cond_t cond;
};
任务task.hpp
#pragma once#include iostream
#include queue
#include pthread.h
#include unistd.hclass task
{
public:task(){}task(int x, int y): x_(x), y_(y){}int operator()(){return x_ y_;}private:int x_;int y_;
}; 测试代码test.cc
#include threadPool.hpp
#include task.hpp
#include iostream
#include ctimeint main()
{srand((unsigned int)time(nullptr) ^ getpid() ^ 12232);ThreadPooltask *tp new ThreadPooltask();tp-run();while (true){int x rand() % 100 1;sleep(1);int y rand() % 100 1;task t(x, y);tp-PushTask(t);cout x y ? endl;}return 0;
}
运行结果 3、线程池的应用场景 1、需要大量的线程来完成任务且完成任务的时间比较短。 2、对性能要求苛刻的应用比如要求服务器迅速响应客户请求。 3、接受突发性的大量请求但不至于使服务器因此产生大量线程的应用。突发性大量客户请求在没有线程池情况下将产生大量线程虽然理论上大部分操作系统线程数目最大值不是问题短时间内产生大量线程可能使内存到达极限出现错误。 二、单例模式的线程安全问题
1、线程池的单例模式
首先我们要做的第一件事就是把构造函数私有再把拷贝构造和赋值运算符重载函数delete
private:ThreadPool(int num THREAD_NUM) : num_(num){for (int i 0; i num_; i){threads_.push_back(new Thread(i, routine, this));}pthread_mutex_init(lock, nullptr);pthread_cond_init(cond, nullptr);}ThreadPool(const TreadPool other) delete;ThreadPool operator(const TreadPool other) delete;
接下来就要在类中定义一个成员变量静态指针方便获取单例对象并在类外初始化
//线程池中的成员变量
private:vectorThread * threads_;int num_;queueT task_queue_;pthread_mutex_t lock;pthread_cond_t cond;static ThreadPoolT *tp;//在类外初始化
template class T
ThreadPoolT *ThreadPoolT::tp nullptr;
最后我们写一个函数可以获取单例对象在设置获取单例对象的函数的时候注意要设置成静态成员函数因为在获取对象前根本没有对象无法调用非静态成员函数无this指针:
static ThreadPoolT *getThreadPool()
{if (tp nullptr){tp new ThreadPoolT();}return tp;
} 2、线程安全问题
上面的线程池的单例模式看起来没有什么问题。可是当我们有多个线程去调用 getThreadPool函数去创建线程池的时候可能会有多个线程同时进入判断判断出线程池指针为空然后创建线程池对象。这样就会创建出多个线程池对象这就不符合我们单例模式的要求了所以我们必须让在同一时刻只有一个线程能够进入判断我们就要用到锁了。
定义一个静态锁并初始化
private:vectorThread * threads_;int num_;queueT task_queue_;pthread_mutex_t lock;pthread_cond_t cond;static ThreadPoolT *tp;static pthread_mutex_t lock;// 类外初始化
template class T
pthread_mutex_t ThreadPoolT::lock PTHREAD_MUTEX_INITIALIZER;
对 getThreadPool函数进行加锁 static ThreadPoolT *getThreadPool(){if (tp nullptr){pthread_mutex_lock(lock);if (tp nullptr){tp new ThreadPoolT();}pthread_mutex_unlock(lock);}return tp;}
对于上面的代码我们为什么要在获取锁之前还要再加一个判断指针为空的条件呢
当已经有一个线程创建出来了线程池的单例模式后在这之后的所有其他线程即使申请到锁紧着着下一步就是去释放锁它不会进入第二个 if 条件里面。其实这样是效率低下的因为线程会频繁申请锁然后就释放锁。所以我们在最外层再加一个if判断就可以阻止后来的线程不用去申请锁创建线程池了直接返回已经创建出来的线程池。
三、其他锁
1、悲观锁在每次取数据时总是担心数据会被其他线程修改所以会在取数据前先加锁读锁写锁行锁等当其他线程想要访问数据时被阻塞挂起。
2、乐观锁每次取数据时候总是乐观的认为数据不会被其他线程修改因此不上锁。但是在更新数据前会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式版本号机制和CAS操作。 ~ CAS操作当需要更新数据时判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败失败则重试一般是一个自旋的过程即不断重试。
3、自旋锁说到自旋锁我们不得不说一说我们之前所用到的锁我们之前所用的锁都是互斥锁当线程没有竞争到互斥锁时它会阻塞等待只有等锁被释放了后才能去重新申请锁。而对于自旋锁当线程没有竞争到自旋锁的时候线程会不断地循环检测去申请自旋锁直到拿到锁。
一般来说如果临界区的代码执行时间比较长的话我们是使用互斥锁而不是自旋锁的这样线程不会因为频繁地检测去申请锁而占用CPU资源。如果临界区的代码执行时间较短的话我们一般就最好使用自旋锁而不是互斥锁因为互斥锁申请失败是要阻塞等待是需要发生上下文切换的如果临界区执行的时间比较短那可能上下文切换的时间会比临界区代码执行的时间还要长。