池化技术本质就是空间换时间的技术,比如我们申请空间的时候,OS 会给我们多分配一些空间,在后续我们扩展空间的时候,直接线程的去访问这些空间。
线程池是一种线程使用模式。线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
线程池的应用场景:
线程池的种类:
线程池示例:
代码实现:
Task.hpp:
#pragma once
#include
#include
#include
class Task
{
public:
Task()
{}
Task(int x, int y, char op) : _x(x), _y(y), _op(op), _result(0), _exitCode(0)
{}
void operator()() // 仿函数
{
switch (_op)
{
case '+':
_result = _x + _y;
break;
case '-':
_result = _x - _y;
break;
case '*':
_result = _x * _y;
break;
case '/':
{
if (_y == 0)
_exitCode = -1;
else
_result = _x / _y;
}
break;
case '%':
{
if (_y == 0)
_exitCode = -2;
else
_result = _x % _y;
}
break;
default:
break;
}
usleep(100000);
}
std::string formatArg()
{
return std::to_string(_x) + _op + std::to_string(_y) + "= ?";
}
std::string formatRes()
{
return std::to_string(_result) + "(" + std::to_string(_exitCode) + ")";
}
~Task()
{}
private:
int _x; // 传入
int _y;
char _op;
int _result; // 传出
int _exitCode;
};
Thread.hpp:
#pragma once
#include
#include
#include
#include
class Thread
{
public:
typedef enum
{
NEW = 0,
RUNNING,
EXITED
} ThreadStatus;
typedef void (*func_t)(void *);
public:
Thread(int num, func_t func, void *args) : _tid(0), _status(NEW), _func(func), _args(args)
{
char name[128];
snprintf(name, sizeof(name), "thread-%d", num);
_name = name;
}
int status() { return _status; }
std::string threadname() { return _name; }
pthread_t threadid()
{
if (_status == RUNNING)
return _tid;
else
{
return 0;
}
}
// 注意 static 函数的特性
static void *runHelper(void *args)
{
Thread *ts = (Thread*)args; // 就拿到了当前对象
// _func(_args);
(*ts)();
return nullptr;
}
void operator ()() //仿函数
{
if(_func != nullptr) _func(_args);
}
void run()
{
int n = pthread_create(&_tid, nullptr, runHelper, this);
if(n != 0) exit(1);
_status = RUNNING;
}
void join()
{
int n = pthread_join(_tid, nullptr);
if( n != 0)
{
std::cerr << "main thread join thread " << _name << " error" << std::endl;
return;
}
_status = EXITED;
}
~Thread()
{}
private:
pthread_t _tid;
std::string _name;
func_t _func; // 线程未来要执行的回调
void *_args;
ThreadStatus _status;
};
lockGuard.hpp:
#pragma once
#include
#include
class Mutex
{
public:
Mutex(pthread_mutex_t *mutex):_pmutex(mutex)
{}
void lock()
{
pthread_mutex_lock(_pmutex);
}
void unlock()
{
pthread_mutex_unlock(_pmutex);
}
~Mutex()
{}
private:
pthread_mutex_t *_pmutex;
};
class LockGuard // 锁由外部传入
{
public:
LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
{
_mutex.lock();
}
~LockGuard()
{
_mutex.unlock();
}
private:
Mutex _mutex;
};
ThreadPool.hpp: (单例模式
#pragma once
#include
#include
#include
#include
#include
#include "Thread.hpp"
#include "Task.hpp"
#include "lockGuard.hpp"
const static int N = 5;
template <class T>
class ThreadPool
{
private:
ThreadPool(int num = N) : _num(num)
{
pthread_mutex_init(&_lock, nullptr);
pthread_cond_init(&_cond, nullptr);
}
ThreadPool(const ThreadPool<T> &tp) = delete;
void operator=(const ThreadPool<T> &tp) = delete;
public:
static ThreadPool<T> *getinstance()
{
if(nullptr == instance) // 为什么要这样?提高效率,减少加锁的次数!
{
LockGuard lockguard(&instance_lock);
if (nullptr == instance)
{
instance = new ThreadPool<T>();
instance->init();
instance->start();
}
}
return instance;
}
pthread_mutex_t *getlock()
{
return &_lock;
}
void threadWait()
{
pthread_cond_wait(&_cond, &_lock);
}
void threadWakeup()
{
pthread_cond_signal(&_cond);
}
bool isEmpty()
{
return _tasks.empty();
}
T popTask()
{
T t = _tasks.front();
_tasks.pop();
return t;
}
static void threadRoutine(void *args)
{
// pthread_detach(pthread_self());
ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
while (true)
{
// 1. 检测有没有任务
// 2. 有:处理
// 3. 无:等待
// 细节:必定加锁
T t;
{
LockGuard lockguard(tp->getlock());
while (tp->isEmpty())
{
tp->threadWait();
}
t = tp->popTask(); // 从公共区域拿到私有区域
}
// for test
t();
std::cout << "thread handler done, result: " << t.formatRes() << std::endl;
// t.run(); // 处理任务,应不应该在临界区中处理?1,0
}
}
void init()
{
for (int i = 0; i < _num; i++)
{
_threads.push_back(Thread(i, threadRoutine, this));
}
}
void start()
{
for (auto &t : _threads)
{
t.run();
}
}
void check()
{
for (auto &t : _threads)
{
std::cout << t.threadname() << " running..." << std::endl;
}
}
void pushTask(const T &t)
{
LockGuard lockgrard(&_lock);
_tasks.push(t);
threadWakeup();
}
~ThreadPool()
{
for (auto &t : _threads)
{
t.join();
}
pthread_mutex_destroy(&_lock);
pthread_cond_destroy(&_cond);
}
private:
std::vector<Thread> _threads;
int _num;
std::queue<T> _tasks; // 使用stl的自动扩容的特性
pthread_mutex_t _lock;
pthread_cond_t _cond;
static ThreadPool<T> *instance;
static pthread_mutex_t instance_lock;
};
template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::instance_lock = PTHREAD_MUTEX_INITIALIZER;
main.cc
#include "ThreadPool.hpp"
#include "Task.hpp"
#include
int main()
{
// 消费者:相当于把消费者包装在了类内部,充当了线程池
// std::unique_ptr> tp(new ThreadPool(20)); // 智能指针
// tp->init();
// tp->start();
// tp->check();
printf("0X%x\n", ThreadPool<Task>::getinstance());
printf("0X%x\n", ThreadPool<Task>::getinstance());
printf("0X%x\n", ThreadPool<Task>::getinstance());
printf("0X%x\n", ThreadPool<Task>::getinstance());
printf("0X%x\n", ThreadPool<Task>::getinstance());
printf("0X%x\n", ThreadPool<Task>::getinstance());
// 生产者
while (true)
{
int x, y;
char op;
std::cout << "please Enter x> ";
std::cin >> x;
std::cout << "please Enter y> ";
std::cin >> y;
std::cout << "please Enter op(+-*/%)> ";
std::cin >> op;
Task t(x, y, op);
ThreadPool<Task>::getinstance()->pushTask(t); //单例对象也有可能在多线程场景中使用!
}
}
消费者会读走数据,读者不会对数据产生影响。这是读写和生产消费的区别!!
三种关系:
- 读者 & 读者 -> 没有关系
- 写者 & 写者 -> 互斥关系
- 读者 & 写者 -> 同步 + 互斥关系
两种角色:
- 读者和写者
一个交易场所:
- 缓冲区(通常是)
注意: 写独占、读共享、读锁优先级更高
读锁(读者加锁)pthread_rwlock_rdlock
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
写锁(写者加锁)pthread_rwlock_wrlock
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
读写解锁 / 释放锁 pthread_rwlock_unlock
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
读写锁初始化 pthread_rwlock_init
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
读写锁的销毁 pthread_rwlock_destroy
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
伪代码如下:
int reader_cnt = 0; // 记录读者在读的数量
pthread_mutex_t lock;
sem_t w(1); // 写者信号量初始值设为 1
读者:
// 读
pthread_mutex_lock(&lock);
if(reader_cnt == 0) P(w); // 如果读者发现此时没有人在读,就拿走写者的信号量,写者就没法写了
reader_cnt++;
读数据
pthread_mutex_un lock(&lock);
// 不读了
pthread_mutex_lock(&lock);
reader_cnt--;
if(reader_cnt == 0) V(w); // 如果没人读了,把写者的信号量还回去
pthread_mutex_un lock(&lock);
写者:
P(w);
if(reader_cnt > 0) // 如果有人读,直接让出信号量
{
V(w);
挂起等待;
}
// 申请到 P 锁了,即没有读者在读了
写入数据
V(w);
读者倘若很多一直不断的读取,导致写者写入不了数据,即写者饥饿,怎么办呢?
读者优先策略,上述代码就是,我们一般都是使用的这个策略。饥饿问题是正常的。
写者优先策略