文章目录
一、引言
1、线程池的定义与作用
在多线程编程中,线程池是一种管理和复用线程的机制。它包含了一个线程集合,用于执行提交给它的任务,而不需要每次任务到来时都创建新的线程。线程池通常由以下几个关键组件组成:任务队列、线程管理模块和线程池管理模块。
2、为什么需要线程池?
-
资源管理优化:线程的创建和销毁是一种昂贵的操作,特别是在高并发的情况下。线程池能够预先创建一定数量的线程,并在需要时重复利用这些线程,从而节省了线程创建和销毁的开销,提高了资源的利用率。
-
任务调度与限制:线程池可以限制并发执行的线程数量,避免系统过载或资源竞争问题。通过控制线程池的大小和任务队列的容量,可以有效地管理系统的并发度。
-
性能提升:线程池能够在一定程度上提升程序的性能。通过合理调整线程池的参数,可以降低线程创建与销毁的开销,减少由于频繁创建线程而可能带来的性能损耗。
-
简化编程模型:使用线程池可以简化多线程编程模型。开发者只需将任务提交到线程池中,而无需关注线程的创建和管理细节,从而降低了多线程编程的复杂性和出错几率。
总之,线程池作为一种高效、可管理的并发编程工具,在多种并发场景下发挥着重要作用,特别是在服务器端和多任务处理系统中具有广泛的应用价值。
二、设计思路与目标
1、分析需求:任务分发与执行
在设计线程池之前,首先需要明确以下几点:
-
任务类型:线程池需要处理的任务类型,可以是简单的函数调用、Lambda表达式或者更复杂的任务对象。
-
任务管理:如何将任务提交到线程池,并且如何管理这些任务的状态和执行结果。
-
线程管理:线程池需要能够动态管理线程的数量,并在需要时启动和关闭线程。
2、设计Thread类的实现:封装线程的创建与管理
线程的启动与关闭:
我们通过封装Thread
类来管理线程
#ifndef __THREAD_HPP__
#define __THREAD_HPP__
#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>
namespace ThreadModule
{
using func_t = std::function<void(const std::string &)>;
class Thread
{
public:
void Excute()
{
_func(_threadname);
}
public:
Thread(func_t func, const std::string &name = "none-name")
: _func(func), _threadname(name), _stop(true) {}
static void *threadroutine(void *args)
// 类成员函数,形参是有this指针的!!
{
Thread *self = static_cast<Thread *>(args);
self->Excute();
return nullptr;
}
bool Start()
{
int n = pthread_create(&_tid, nullptr, threadroutine, this);
if (!n)
{
_stop = false;
return true;
}
else
{
return false;
}
}
void Detach()
{
if (!_stop)
{
pthread_detach(_tid);
}
}
void Join()
{
if (!_stop)
{
pthread_join(_tid, nullptr);
}
}
std::string name()
{
return _threadname;
}
void Stop()
{
_stop = true;
}
~Thread() {}
private:
pthread_t _tid;
std::string _threadname;
func_t _func;
bool _stop;
};
} // namespace ThreadModule
#endif
3、设计Task类的实现:封装任务的执行单元
任务的类型与执行逻辑:
- 任务接口的设计:定义一个统一的任务接口,可以是函数对象、Lambda表达式或者自定义的任务对象。
- 任务的执行逻辑:确定任务执行时的逻辑,例如参数传递、异常处理、任务完成通知等。
下面是我定义的简单任务接口:
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cmath>
#include <sstream>
#include <iomanip>
std::string formatResult(double _a, double _b, std::string opStr, double _result)
{
std::ostringstream oss;
oss << std::fixed << std::setprecision(2) << _a << opStr << _b << "=" << _result;
return oss.str();
}
enum Operation
{
ADD = 0,
SUBTRACT,
MULTIPLY,
DIVIDE
};
class Task
{
public:
Task(int a = 1, int b = 1, int op = ADD) : _a(a), _b(b), _op(op), _result(0) {}
void Execute()
{
switch (_op)
{
case ADD:
_result = static_cast<double>(_a + _b);
break;
case SUBTRACT:
_result = static_cast<double>(_a - _b);
break;
case MULTIPLY:
_result = static_cast<double>(_a * _b);
break;
case DIVIDE:
if (_b != 0)
{
_result = static_cast<double>(_a) / _b;
}
else
{
std::cerr << "错误:除以零!" << std::endl;
}
break;
default:
std::cerr << "错误:未知操作!" << std::endl;
}
}
std::string ResultToString() const
{
std::string opStr;
switch (_op)
{
case Operation::ADD:
opStr = "+";
break;
case Operation::SUBTRACT:
opStr = "-";
break;
case Operation::MULTIPLY:
opStr = "*";
break;
case Operation::DIVIDE:
opStr = "/";
break;
default:
opStr = "?";
break;
}
return formatResult(_a, _b, opStr, _result);
}
std::string DebugToString() const
{
std::string opStr;
switch (_op)
{
case Operation::ADD:
opStr = "+";
break;
case Operation::SUBTRACT:
opStr = "-";
break;
case Operation::MULTIPLY:
opStr = "*";
break;
case Operation::DIVIDE:
opStr = "/";
break;
default:
opStr = "?";
break;
}
return std::to_string(_a) + opStr + std::to_string(_b) + "=?";
}
// 重载 () 运算符,允许任务对象像函数对象一样调用
void operator()() { Execute(); }
// 获取任务操作的结果
double GetResult() const { return _result; }
private:
int _a; // 操作的第一个操作数
int _b; // 操作的第二个操作数
double _result; // 操作的结果
int _op; // 操作类型 (ADD, SUBTRACT, MULTIPLY, DIVIDE)
};
通过对Thread
类和Task
类的设计与实现,可以为后续的线程池设计奠定基础,确保任务能够被安全地提交到线程池中执行,并能够有效地管理线程资源和任务执行状态。
4、实现LOG宏定义
日志宏的设计与实现:
#pragma once
#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
class LockGuard
{
public:
LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
{
pthread_mutex_lock(_mutex);
// 构造加锁
}
~LockGuard()
{
pthread_mutex_unlock(_mutex);
}
private:
pthread_mutex_t *_mutex;
};
const std::string logname = "log.txt";
enum Level
{
DEBUG = 0,
INFO,
WARNING,
ERROR,
FATAL
};
std::string LevelToString(int level)
{
switch (level)
{
case DEBUG:
return "Debug";
case INFO:
return "Info";
case WARNING:
return "Warning";
case ERROR:
return "Error";
case FATAL:
return "Fatal";
default:
return "Unknown";
}
}
std::string GetTimeString()
{
time_t curr_time = time(nullptr);
struct tm *format_time = localtime(&curr_time);
if (format_time == nullptr)
return "None";
char time_buffer[1024];
snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
format_time->tm_year + 1900,
format_time->tm_mon + 1,
format_time->tm_mday,
format_time->tm_hour,
format_time->tm_min,
format_time->tm_sec);
return time_buffer;
}
void SaveFile(const std::string &filename, const std::string &message)
{
std::ofstream out(filename, std::ios::app);
if (!out.is_open())
{
return;
}
out << message;
out.close();
}
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{
std::string levelstr = LevelToString(level);
std::string timestr = GetTimeString();
pid_t selfid = getpid();
char buffer[1024];
va_list arg;
va_start(arg, format);
vsnprintf(buffer, sizeof(buffer), format, arg);
va_end(arg);
std::string message = "[" + timestr + "]" +
"[" + levelstr + "]" +
"[" + std::to_string(selfid) + "]" +
"[" + filename + "]" +
"[" + std::to_string(line) + "] " + buffer + "\n";
LockGuard lockguard(&lock);
if (!issave)
{
std::cout << message;
}
else
{
SaveFile(logname, message);
}
}
bool gIsSave = 0;
#define LOG(level, format, ...) \
do \
{ \
LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__); \
} while (0)
#define EnableFile() \
do \
{ \
gIsSave = true; \
} while (0)
#define EnableScreen() \
do \
{ \
gIsSave = false; \
} while (0)
日志级别与输出格式控制:
日志级别通过枚举类型 Level
定义,用于标识不同严重性的日志。日志格式包括时间、日志级别、进程ID、文件名、行号和具体日志信息,以便于追溯和调试。
日志的异步输出与性能考虑:
在日志的实现中,通过互斥锁 LockGuard
来保护对共享资源的访问,避免多线程环境下的竞争条件。同时,提供了异步输出到文件的能力,通过 SaveFile()
函数将日志信息追加到文件末尾,确保性能和线程安全性的平衡。
这段代码实现了一个简单的多线程安全的日志系统,通过宏定义和函数封装提供了方便的接口来记录和管理日志。它考虑了多线程环境下的数据竞争问题,并提供了动态切换日志输出目标的能力,方便在不同场景下使用和配置。
三、线程池的核心实现
线程池的基本结构与成员变量:
线程池的基本结构由以下成员变量组成:
template <class T>
class ThreadPool
{
// 禁止赋值运算符和拷贝构造函数,确保线程池实例不能被复制或赋值
void LockQueue(); // 加锁任务队列的互斥锁
void UnlockQueue(); // 解锁任务队列的互斥锁
void ThreadSleep(); // 线程进入睡眠等待状态
void ThreadWakeup(); // 唤醒一个处于睡眠状态的线程
void ThreadWakeupAll(); // 唤醒所有处于睡眠状态的线程
ThreadPool operator=(const ThreadPool<T> &) = delete; // 禁止赋值运算符
ThreadPool(const ThreadPool<T> &) = delete; // 禁止拷贝构造函数
// 构造函数,初始化线程池对象
ThreadPool(int threadnum = gdefaultthreadnum);
// 处理任务的函数,线程池中的工作线程会调用此函数执行任务
void HandlerTask(const std::string &name);
// 初始化线程池,创建指定数量的工作线程并启动
void InitThreadPool();
public:
// 获取线程池单例实例,如果不存在则创建新实例并返回
static std::unique_ptr<ThreadPool<T>> &GetInstance(int num = gdefaultthreadnum);
// 将任务加入任务队列,等待线程池中的线程执行
bool Enqueue(const T &t);
// 启动线程池,使得线程池中的线程可以开始执行任务
void Start();
// 停止线程池,不再接收新任务,并等待已有任务执行完成
void Stop();
// 等待线程池中的所有线程执行完成
void Wait();
// 析构函数,释放线程池中的资源,包括互斥锁、条件变量等
~ThreadPool();
private:
int _threadnum; // 线程池中线程的数量
std::vector<Thread> _threads; // 线程池中的线程对象数组
std::queue<T> _task_queue; // 任务队列,存放需要执行的任务
pthread_mutex_t _mutex; // 互斥锁,保护任务队列的访问
pthread_cond_t _cond; // 条件变量,用于线程间的同步操作
int _waitnum = 0; // 等待执行任务的线程数目
bool _isrunning = false; // 线程池的运行状态标志
static std::unique_ptr<ThreadPool<T>> _instance; // 线程池的单例实例
static pthread_mutex_t _lock; // 控制单例实例访问的互斥锁
};
template <class T>
std::unique_ptr<ThreadPool<T>> ThreadPool<T>::_instance = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;
// 静态成员,用于实现线程池的单例模式,保证全局只有一个线程池实例,并且线程安全。
线程池的初始化与资源管理
线程池的初始化和资源管理包括以下几个关键点:
ThreadPool(int threadnum = gdefaultthreadnum)
: _threadnum(threadnum)
{
pthread_mutex_init(&_mutex, nullptr); // 初始化互斥锁
pthread_cond_init(&_cond, nullptr); // 初始化条件变量
LOG(INFO, "ThreadPool Construct()");
// 构造函数中进行线程池的初始化工作,包括初始化线程数量和日志记录
}
void InitThreadPool()
{
for (int num = 0; num < _threadnum; num++)
{
std::string name = "thread-" + std::to_string(num + 1);
std::function<void(const std::string &)> fun = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);
_threads.emplace_back(fun, name); // 创建线程对象,并添加到线程池中的线程数组
LOG(INFO, "init thread %s done", name.c_str());
}
// 初始化线程池,创建指定数量的工作线程,并初始化线程名和任务处理函数
}
- 在构造函数中,会根据传入的
threadnum
初始化线程池的基本参数,并进行互斥锁和条件变量的初始化。 InitThreadPool
函数负责创建_threadnum
个工作线程,并将它们添加到线程池中的_threads
数组中,每个线程都有一个名字和一个处理任务的函数。
任务队列的管理与调度
任务队列的管理和调度是线程池中的核心功能之一,确保任务能够被有效地提交、调度和执行。
void HandlerTask(const std::string &name)
{
LOG(INFO, "%s is running...", name.c_str());
while (true)
{
LockQueue();
while (_task_queue.empty() && _isrunning)
{
_waitnum++;
ThreadSleep();
_waitnum--;
}
if (_task_queue.empty() && !_isrunning)
{
UnlockQueue();
sleep(1);
LOG(INFO, "%s break while ...", name.c_str());
break;
}
T task = _task_queue.front();
_task_queue.pop();
UnlockQueue();
LOG(DEBUG, "%s get a task", name.c_str());
task();
LOG(DEBUG, "%s handler a task, result is: %s", name.c_str(), task.ResultToString().c_str());
}
}
HandlerTask
函数是线程池中线程实际执行任务的函数。每个工作线程执行该函数,不断从任务队列 _task_queue
中取出任务并执行,直到线程池停止运行。在函数中使用了互斥锁保护 _task_queue
的访问,确保多个线程同时操作任务队列时不会发生竞态条件。使用条件变量 _cond
来实现线程的等待和唤醒机制,当任务队列为空且线程池正在运行时,线程进入等待状态;当有新任务加入时,唤醒一个等待的线程进行任务处理。
线程池的状态控制
线程池的状态控制保证了线程池的稳定运行和异常处理能力。
void Start()
{
_isrunning = true;
for (auto &thread : _threads)
{
thread.Start(); // 启动所有工作线程
}
}
void Stop()
{
LockQueue();
_isrunning = false; // 停止线程池运行
ThreadWakeupAll(); // 唤醒所有等待中的线程
UnlockQueue();
}
void Wait()
{
for (auto &thread : _threads)
{
thread.Join(); // 等待所有工作线程结束
LOG(INFO, "%s is quit...", thread.name().c_str());
}
}
Start
函数用于启动线程池,将 _isrunning
设置为 true,并依次启动所有的工作线程。Stop
函数停止线程池的运行,将 _isrunning
设置为false
,唤醒所有等待中的线程,确保线程池能够安全退出。Wait
函数用于等待所有工作线程结束,并记录线程退出的信息。
实现任务提交的接口
bool Enqueue(const T &task)
{
bool ret = false;
LockQueue();
if (_isrunning)
{
_task_queue.push(task); // 将任务加入任务队列
if (_waitnum > 0)
{
ThreadWakeup(); // 唤醒等待中的线程
}
LOG(DEBUG, "enqueue task success");
ret = true;
}
UnlockQueue();
return ret;
}
Enqueue
函数用于向线程池提交任务。首先获取互斥锁保护任务队列的操作,将任务 task
加入 _task_queue
中。如果线程池正在运行且任务队列非空,唤醒一个等待中的线程处理任务。返回 true
表示任务成功加入任务队列,否则返回 false
。
获得线程池单例的函数:
我们在第一次获得单例的时候,完成对工作线程的初始化,并且启动了线程池。
static std::unique_ptr<ThreadPool<T>> &GetInstance(int num = gdefaultthreadnum)
{
if (!_instance)
{
LockGuard lockguard(&_lock);
if (!_instance)
{
LOG(DEBUG, "creating a ThreadPool instance.");
_instance = std::unique_ptr<ThreadPool<T>>(new ThreadPool<T>(num));
_instance->InitThreadPool();
_instance->Start();
}
}
else
{
LOG(DEBUG, "returning the existing instance of the ThreadPool.");
}
return _instance;
}
线程池类实现了单例模式,确保在整个程序生命周期内只存在一个线程池实例。这通过 GetInstance
函数实现,使用了双重检查锁定机制来保证线程安全地创建和获取单例实例。第一次检查 _instance
是否为空,如果为空,才会进入互斥锁的保护区域。
- 双重检查锁定,这是一种常见的单例模式实现方式。其目的是在多线程环境下安全地创建和获取单例实例,同时尽可能减少互斥锁的竞争,提高性能。
单例模式的实现中,使用了 pthread_mutex_t
来保护静态成员 _instance
的创建过程,确保在多线程环境中也能正确地返回单例实例。函数最后返回 _instance
,这是一个 std::unique_ptr<ThreadPool<T>>&
类型的引用。这样做的好处是,外部调用 GetInstance
函数可以直接操作和使用线程池的唯一实例。
完整代码:
#pragma once
#include <iostream>
#include <vector>
#include <queue>
#include <memory>
#include <pthread.h>
#include "Log.hpp"
#include "Thread.hpp"
#include "LockGuard.hpp"
using namespace ThreadModule;
const static int gdefaultthreadnum = 3;
template <class T>
class ThreadPool
{
void LockQueue() { pthread_mutex_lock(&_mutex); }
void UnlockQueue() { pthread_mutex_unlock(&_mutex); }
void ThreadSleep() { pthread_cond_wait(&_cond, &_mutex); }
void ThreadWakeup() { pthread_cond_signal(&_cond); }
void ThreadWakeupAll() { pthread_cond_broadcast(&_cond); }
ThreadPool operator=(const ThreadPool<T> &) = delete;
ThreadPool(const ThreadPool<T> &) = delete;
ThreadPool(int threadnum = gdefaultthreadnum)
: _threadnum(threadnum)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
LOG(INFO, "ThreadPool Construct()");
}
void HandlerTask(const std::string &name)
{
LOG(INFO, "%s is running...", name.c_str());
while (true)
{
LockQueue();
while (_task_queue.empty() && _isrunning)
{
_waitnum++;
ThreadSleep();
_waitnum--;
}
if (_task_queue.empty() && !_isrunning)
{
UnlockQueue();
sleep(1);
LOG(INFO, "%s break while ...", name.c_str());
break;
}
T t = _task_queue.front();
_task_queue.pop();
UnlockQueue();
LOG(DEBUG, "%s get a task", name.c_str());
t();
LOG(DEBUG, "%s handler a task, result is: %s", name.c_str(), t.ResultToString().c_str());
}
}
void InitThreadPool()
{
for (int num = 0; num < _threadnum; num++)
{
std::string name = "thread-" + std::to_string(num + 1);
std::function<void(const std::string &)> fun = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);
_threads.emplace_back(fun, name);
LOG(INFO, "init thread %s done", name.c_str());
}
}
public:
static std::unique_ptr<ThreadPool<T>> &GetInstance(int num = gdefaultthreadnum)
{
if (!_instance)
{
LockGuard lockguard(&_lock);
if (!_instance)
{
LOG(DEBUG, "creating a ThreadPool instance.");
_instance = std::unique_ptr<ThreadPool<T>>(new ThreadPool<T>(num));
// _instance = std::make_unique<ThreadPool<T>>(num);
_instance->InitThreadPool();
_instance->Start();
}
}
else
{
LOG(DEBUG, "returning the existing instance of the ThreadPool.");
}
return _instance;
}
bool Enqueue(const T &t)
{
bool ret = false;
LockQueue();
if (_isrunning)
{
_task_queue.push(t);
if (_waitnum > 0)
{
ThreadWakeup();
}
LOG(DEBUG, "enqueue task success");
ret = true;
}
UnlockQueue();
return ret;
}
void Start()
{
_isrunning = true;
for (auto &thread : _threads)
{
thread.Start();
}
}
void Stop()
{
LockQueue();
_isrunning = false;
ThreadWakeupAll();
UnlockQueue();
}
void Wait()
{
for (auto &thread : _threads)
{
thread.Join();
LOG(INFO, "%s is quit...", thread.name().c_str());
}
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
LOG(INFO, "ThreadPool Destory()");
}
private:
int _threadnum;
std::vector<Thread> _threads;
std::queue<T> _task_queue;
pthread_mutex_t _mutex;
pthread_cond_t _cond;
int _waitnum = 0;
bool _isrunning = false;
// 单例 hunger
static std::unique_ptr<ThreadPool<T>> _instance;
static pthread_mutex_t _lock;
};
template <class T>
std::unique_ptr<ThreadPool<T>> ThreadPool<T>::_instance = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;
我设计的的线程池中,使用互斥锁 _mutex
和条件变量 _cond
实现线程同步,确保多线程环境下任务队列的安全访问和线程的同步执行。并设计合理的任务队列 _task_queue
和工作线程数组 _threads
,实现任务的提交、调度和执行,保证线程池的高效运行。
四、线程池的使用
#include "ThreadPool.hpp"
#include "Task.hpp"
#include "Log.hpp"
#include <iostream>
#include <ctime>
int main()
{
srand(time(nullptr) ^ getpid() ^ pthread_self());
LOG(INFO, "beginning-------------------------------");
EnableScreen();
int tasknum = 10;
while (tasknum--)
{
int a = rand() % 50 + 1;
// usleep(124);
int b = rand() % 20 + 1;
// usleep(124);
int c = rand() % 3 + 1;
Task tmp(a, b, c);
LOG(INFO, "main thread push task: %s", tmp.DebugToString().c_str());
ThreadPool<Task>::GetInstance()->Enqueue(tmp);
sleep(1);
}
ThreadPool<Task>::GetInstance()->Stop();
sleep(1);
ThreadPool<Task>::GetInstance()->Wait();
sleep(1);
LOG(INFO, "ending----------------------------------");
return 0;
}
这段代码通过使用 ThreadPool
类和 Task
类,演示了如何创建多个任务并提交到线程池中执行,同时通过日志记录了任务的执行过程和程序的开始与结束。通过这种方式,可以有效地利用多线程处理任务,提高程序的并发性能和效率。