高并发程序开发中我们经常使用到线程池, 无论Java语言还是C++, 我们就拿Java语言的线程池举例, 面试八股文中通常都背诵的滚瓜烂熟, 比如, 核心线程池数量(core size), 最大线程池数量, 超时时间, 队列长度, 以及拒绝策略等等面试官可能都会问到, 但是今天广联达的面试官问到我线程池底层是怎么实现的? 来一个任务它是直接被调度起来, 还是放到队列中去的? 这点之前我确实没仔细琢磨过(没看过Java线程池的底层的实现原理), 今天带着这个问题, 我阅读了一下Java线程池的底层实现, 并且使用C++手写了一个简单的线程池, 这里记录一下实现的过程及一些细节问题.
这点相信不用多说, Java线程中我们都会实现Runnable接口, 然后线程调用start()方法之后会调用我们传递进去的runnable接口的run()方法, C++这里我也做了一个实现:
#ifndef RUNNABLE_H
#define RUNNABLE_H
#include
class runnable {
public:
virtual void run() = 0;
~runnable() {
std::cout << "destroy runnable object" << std::endl;
}
};
#endif
当然也可以使用模板类定义一些有参的run()
方法, 这些都无关紧要.
信号量类: 目的是为了方式队列中任务数量为空, 导致while(true)
代码块出现空转, 占用CPU资源;
锁类: 目的是保证多个线程从队列中取任务和添加任务到队列中访问是互斥的;
#ifndef _LOCKER_H_
#define _LOCKER_H_
#include
#ifdef __APPLE__
#include
#else
#include
#endif
#include
class locker {
private:
pthread_mutex_t mutex;
public:
locker() {
if (pthread_mutex_init(&(this->mutex), nullptr) != 0) {
throw std::exception();
}
}
~locker() {
pthread_mutex_destroy(&(this->mutex));
}
bool lock() {
return pthread_mutex_lock(&(this->mutex)) == 0;
}
bool unlock() {
return pthread_mutex_unlock(&(this->mutex)) == 0;
}
};
class sem {
public:
sem() {
#ifdef __APPLE__
m_sem = dispatch_semaphore_create(0);
#else
if (sem_init(&m_sem, 0, 0) != 0) {
throw std::exception();
}
#endif
}
sem(int init_num) {
#ifdef __APPLE__
m_sem = dispatch_semaphore_create(init_num);
#else
if (sem_init(&(this->m_sem), 0, init_num) != 0) {
throw std::exception();
}
#endif
}
~sem() {
#ifdef __APPLE__
#else
sem_destroy(&m_sem);
#endif
}
bool wait() {
#ifdef __APPLE__
return dispatch_semaphore_wait(m_sem, DISPATCH_TIME_FOREVER) == 0;
#else
return sem_wait(&(this->m_sem)) == 0;
#endif
}
bool post() {
#ifdef __APPLE__
return dispatch_semaphore_signal(m_sem) == 0;
#else
return sem_post(&(this->m_sem)) == 0;
#endif
}
private:
#ifdef __APPLE__
dispatch_semaphore_t m_sem;
#else
sem_t m_sem;
#endif
};
#endif
踩坑点: MAC OS系统没有对sem_post(), sem_wait(), sem_init(), sem_destroy()
这几个方法进行实现, 因此在class sem
类中定义了一些宏, 用于替代这几个方法的实现.
实现逻辑简介:
task_queue
中抢任务执行;semaphore = 0
, 因此所有的线程都阻塞着在;signal(semaphore)
会唤醒相应数量的线程从队列中取数据执行;实现代码:
#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include
#include
#include
#include "locker.h"
#include "runnable.h"
class threadpool {
public:
threadpool(int thread_number, int max_request);
~threadpool();
bool append(std::shared_ptr<runnable> task);
private:
int thread_number; // 线程数量
pthread_t *threads; // 线程数量数组, 大小等于 thread_number
int max_requests; // 队列中最大允许的任务数量
std::queue<std::shared_ptr<runnable>> m_queue; // 任务队列
sem m_sem; // 任务队列存取的信号量, 值表示当前队列中剩余等待调度的任务数量
locker m_queue_locker; // 队列写锁
static void *worker(void *arg); // c++线程执行的具体方法
void run();
};
#endif
/**
* @brief Construct a new threadpool::threadpool object
* 有参 初始化线程池
* @param thread_number 初始线程数量
* @param max_request 最大队列数量
*/
threadpool::threadpool(int thread_number, int max_request) {
if (thread_number <= 0 || max_request <= 0) {
throw std::exception();
}
this->max_requests = max_request;
this->threads = new pthread_t[thread_number];
if (!this->threads) {
throw std::exception();
}
for (int i = 0; i < thread_number; i++) {
if (pthread_create(this->threads + i, nullptr, worker, this) != 0) {
// create ans start thread failed
delete[] this->threads;
throw std::exception();
}
if (pthread_detach(this->threads[i]) != 0) {
delete[] this->threads;
throw std::exception();
}
}
}
threadpool::~threadpool() {
delete[] this->threads;
}
/**
* @brief 线程池执行函数体
*
* @param arg
* @return void*
*/
void *threadpool::worker(void *arg) {
threadpool *pool = (threadpool *)arg;
pool->run();
return pool;
}
/**
* @brief 添加一个任务到线程池的队列中
*
* @param task 任务指针
* @return true
* @return false
*/
bool threadpool::append(std::shared_ptr<runnable> task) {
this->m_queue_locker.lock(); // 阻塞获取锁
if (this->m_queue.size() >= this->max_requests) {
this->m_queue_locker.unlock();
return false;
}
this->m_queue.push(task);
this->m_queue_locker.unlock();
this->m_sem.post();
return true;
}
void threadpool::run() {
while (true) {
this->m_sem.wait();
this->m_queue_locker.lock();
if (this->m_queue.empty()) {
this->m_queue_locker.unlock();
continue;
}
auto task = this->m_queue.front();
this->m_queue.pop();
this->m_queue_locker.unlock();
task->run();
}
}
测试代码:
#include
#include "threadpool.h"
class my_runnable : public runnable {
public:
my_runnable() {
}
void run() override {
std::cout << "hello runnable" << std::endl;
sleep(3);
}
};
int main() {
threadpool *pool = new threadpool(3, 10);
for (int i = 0; i < 4; i++) {
std::shared_ptr<runnable> task(new my_runnable());
pool->append(task);
}
sleep(20);
delete pool;
return 0;
}
append()
方法中添加相应的实现, 例如: 超过max_request值可直接throw std::exception()
, 或者直接调用runnable
接口中的run()
方法, 这样就把main线程给阻塞掉了, 这也是Java线程池中常用的拒绝策略.其它的未考虑到的点欢迎大家批评指正
Github代码链接