- 线程池初始化相关代码声明在
https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/threadutil/ThreadPool.h。线程池配置相关代码位置:https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/inc/config.h#L76-L105。
相关数据结构:
- 以下是代码中几个主要结构体的作用说明,它们涉及线程池的配置、任务和统计等功能:
ThreadPoolAttr用于配置线程池的属性,如线程的最小、最大数量,空闲时间等。ThreadPoolJob定义了线程池中的任务,包括任务的执行函数、参数和优先级。ThreadPoolStats用于记录线程池的统计信息,如任务执行时间、任务队列情况等。ThreadPool是线程池的核心结构,管理线程、任务队列以及线程池的运行状态。
/*! 线程池的属性。用于设置和修改线程池的参数。 */
typedef struct THREADPOOLATTR
{
/*! 线程池始终至少保持这个数量的线程。 */
int minThreads;
/*! 线程池的线程数永远不会超过此数量。 */
int maxThreads;
/*! 分配给每个线程的最小栈大小。 */
size_t stackSize;
/*! 线程在终止前保持空闲的最长时间(以毫秒为单位)。 */
int maxIdleTime;
/*! 每个线程维护的任务数。 */
int jobsPerThread;
/*! 总共可以排队的最大任务数量。 */
int maxJobsTotal;
/*! 低优先级或中等优先级的任务在被提升优先级之前的等待时间(以毫秒为单位)。 */
int starvationTime;
/*! 使用的调度策略。 */
PolicyType schedPolicy;
} ThreadPoolAttr;
/*! 内部线程池任务。 */
typedef struct THREADPOOLJOB
{
start_routine func; // 任务的执行函数
void *arg; // 传递给任务函数的参数
free_routine free_func; // 释放资源的回调函数
struct timeval requestTime; // 任务请求的时间
ThreadPriority priority; // 任务优先级
int jobId; // 任务ID
} ThreadPoolJob;
/*! 用于存储统计数据的结构体。 */
typedef struct TPOOLSTATS
{
double totalTimeHQ; // 高优先级任务的总处理时间
int totalJobsHQ; // 已处理的高优先级任务总数
double avgWaitHQ; // 高优先级任务的平均等待时间
double totalTimeMQ; // 中等优先级任务的总处理时间
int totalJobsMQ; // 已处理的中等优先级任务总数
double avgWaitMQ; // 中等优先级任务的平均等待时间
double totalTimeLQ; // 低优先级任务的总处理时间
int totalJobsLQ; // 已处理的低优先级任务总数
double avgWaitLQ; // 低优先级任务的平均等待时间
double totalWorkTime; // 总工作时间
double totalIdleTime; // 总空闲时间
int workerThreads; // 工作线程的数量
int idleThreads; // 空闲线程的数量
int persistentThreads; // 持久线程的数量
int totalThreads; // 线程总数
int maxThreads; // 最大线程数量
int currentJobsHQ; // 当前高优先级任务数
int currentJobsLQ; // 当前低优先级任务数
int currentJobsMQ; // 当前中等优先级任务数
} ThreadPoolStats;
/*!
* \brief 一个类似于 UPnP SDK 中的线程池。
*
* 允许线程池中的线程调度和运行任务。线程池初始化时设置了最小和最大线程数、
* 最大空闲时间以及每线程的任务比率。如果一个工作线程在最大空闲时间内未接收到任务,
* 且当前线程池的运行线程数超过最小线程数,则该工作线程将退出。如果调度任务时,
* 当前任务与线程的比率超过设定比率,且线程池的线程数少于最大值,则将创建一个新线程。
*/
typedef struct THREADPOOL
{
/*! 保护任务队列的互斥锁。是对pthread_mutex_t(🔒对象)的封装 */
ithread_mutex_t mutex;
/*! 用于任务队列信号的条件变量。条件变量是一种允许线程等待特定条件的机制,直到满足某些条件才允许线程在访问共享资源之前进行访问,线程被唤醒,重新获取互斥锁并继续执行。https://blog.youkuaiyun.com/ResumeProject/article/details/129522548 */
ithread_cond_t condition;
/*! 用于启动和关闭的条件变量。 */
ithread_cond_t start_and_shutdown;
/*! 任务ID计数器。 */
int lastJobId;
/*! 标识线程池是否正在关闭。 */
int shutdown;
/*! 线程总数。 */
int totalThreads;
/*! 是否在等待新工作线程启动。 */
int pendingWorkerThreadStart;
/*! 当前正在执行任务的线程数量。 */
int busyThreads;
/*! 持久线程的数量。 */
int persistentThreads;
/*! 任务的空闲列表。 */
FreeList jobFreeList;
/*! 低优先级任务队列。 */
LinkedList lowJobQ;
/*! 中等优先级任务队列。 */
LinkedList medJobQ;
/*! 高优先级任务队列。 */
LinkedList highJobQ;
/*! 持久任务。 */
ThreadPoolJob *persistentJob;
/*! 线程池的属性。 */
ThreadPoolAttr attr;
/*! 统计数据。 */
ThreadPoolStats stats;
} ThreadPool;
UpnpInitThreadPools
- UpnpInitThreadPools()(无参数)为预初始化直接调用的函数。
/*!
* \brief Initializes the global threadm pools used by the UPnP SDK.
*
* \return UPNP_E_SUCCESS on success or UPNP_E_INIT_FAILED if a mutex could not
* be initialized.
*/
static int UpnpInitThreadPools(void)
{
int ret = UPNP_E_SUCCESS;
ThreadPoolAttr attr;
TPAttrInit(&attr); // 初始化属性,然后设置线程池的最大最小线程数,每个线程的JOB数量等等
TPAttrSetMaxThreads(&attr, MAX_THREADS);
TPAttrSetMinThreads(&attr, MIN_THREADS);
TPAttrSetStackSize(&attr, THREAD_STACK_SIZE);
TPAttrSetJobsPerThread(&attr, JOBS_PER_THREAD);
TPAttrSetIdleTime(&attr, THREAD_IDLE_TIME);
TPAttrSetMaxJobsTotal(&attr, MAX_JOBS_TOTAL);
// 然后利用以上属性初始化三个全局线程池
if (ThreadPoolInit(&gSendThreadPool, &attr) != UPNP_E_SUCCESS) {
ret = UPNP_E_INIT_FAILED;
goto exit_function;
}
if (ThreadPoolInit(&gRecvThreadPool, &attr) != UPNP_E_SUCCESS) {
ret = UPNP_E_INIT_FAILED;
goto exit_function;
}
if (ThreadPoolInit(&gMiniServerThreadPool, &attr) != UPNP_E_SUCCESS) {
ret = UPNP_E_INIT_FAILED;
goto exit_function;
}
exit_function:
if (ret != UPNP_E_SUCCESS) {
UpnpSdkInit = 0;
UpnpFinish();
}
return ret;
}
函数ThreadPoolInit初始化并启动线程池
函数声明
// https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/threadutil/ThreadPool.h#L252-L280
/*!
* \brief 初始化并启动线程池。必须首先调用此函数,并且只能调用一次以初始化线程池。
*
* \return
* \li \c 0 表示成功。
* \li \c EAGAIN 表示系统资源不足,无法创建最小数量的线程。
* \li \c INVALID_POLICY 表示无法设置调度策略。
* \li \c EMAXTHREADS 表示最小线程数大于最大线程数。
*/
int ThreadPoolInit(
/*! 必须是有效的、非空的 ThreadPool 指针。 */
ThreadPool *tp,
/*! 可以为 NULL。如果不为 NULL,则 attr 包含以下字段:
* \li \c minWorkerThreads - 线程池中最少的工作线程数量,线程池不会少于这个数量的线程。
* \li \c maxWorkerThreads - 线程池中最多的工作线程数量,线程池不会超过这个数量的线程。
* \li \c maxIdleTime - 工作线程保持空闲的最长时间。如果线程空闲时间超过此值且
* 运行中的线程数超过最小数量,则该工作线程退出。
* \li \c jobsPerThread - 每个线程要维护的任务与线程的比率。如果调度任务时每个线程的任务
* 数超过这个比率,并且运行中的工作线程数少于最大线程数,则启动一个新线程以提高效率。
* \li \c schedPolicy - 尝试设置的调度策略(依赖于操作系统)。
*/
ThreadPoolAttr *attr);
函数定义
- 先进行线程池加锁,然后初始化线程池tp,后续解锁。

// https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/threadutil/ThreadPool.c#L710-L776
int ThreadPoolInit(ThreadPool *tp, ThreadPoolAttr *attr)
{
int retCode = 0; // 用于存储返回的错误值
int i = 0; // 计数器
// 检查线程池指针是否为空,如果为空则返回错误代码 EINVAL
if (!tp) {
return EINVAL;
}
// 初始化线程池的互斥锁,并将其加锁
retCode += ithread_mutex_init(&tp->mutex, NULL);// pthread_mutex_init
retCode += ithread_mutex_lock(&tp->mutex);
// 初始化线程池的条件变量
retCode += ithread_cond_init(&tp->condition, NULL);
retCode += ithread_cond_init(&tp->start_and_shutdown, NULL);
// 如果初始化失败,解锁并销毁互斥锁和条件变量,返回错误代码 EAGAIN
if (retCode) {
ithread_mutex_unlock(&tp->mutex);
ithread_mutex_destroy(&tp->mutex);
ithread_cond_destroy(&tp->condition);
ithread_cond_destroy(&tp->start_and_shutdown);
return EAGAIN;
}
// 如果提供了线程池属性,则使用该属性,否则初始化默认属性
if (attr) {
tp->attr = *attr;
} else {
TPAttrInit(&tp->attr);
}
// 检查并设置调度策略(和操作系统相关,[sched_getparam 手册页](https://man7.org/linux/man-pages/man2/sched_getparam.2.html))
if (SetPolicyType(tp->attr.schedPolicy) != 0) {
// 如果设置失败,则返回错误代码 INVALID_POLICY
ithread_mutex_unlock(&tp->mutex);
ithread_mutex_destroy(&tp->mutex);
ithread_cond_destroy(&tp->condition);
ithread_cond_destroy(&tp->start_and_shutdown);
return INVALID_POLICY;
}
// 初始化线程池的作业空闲列表和统计信息
retCode += FreeListInit(&tp->jobFreeList, sizeof(ThreadPoolJob), JOBFREELISTSIZE);
StatsInit(&tp->stats);
// 初始化线程池的高、中、低优先级作业队列
retCode += ListInit(&tp->highJobQ, CmpThreadPoolJob, NULL);
retCode += ListInit(&tp->medJobQ, CmpThreadPoolJob, NULL);
retCode += ListInit(&tp->lowJobQ, CmpThreadPoolJob, NULL);
// 如果初始化失败,则返回 EAGAIN 错误代码
if (retCode) {
retCode = EAGAIN;
} else {
// 初始化线程池的作业和线程计数信息
tp->persistentJob = NULL;
tp->lastJobId = 0;
tp->shutdown = 0;
tp->totalThreads = 0;
tp->busyThreads = 0;
tp->persistentThreads = 0;
tp->pendingWorkerThreadStart = 0;
// ********** 创建最小数量的线程 **********
for (i = 0; i < tp->attr.minThreads; ++i) {
retCode = CreateWorker(tp); // 创建一个工作线程
if (retCode) {
break;
}
}
}
// 解锁互斥锁
ithread_mutex_unlock(&tp->mutex);
// 如果线程创建失败,则清理线程池并关闭
if (retCode) {
/* 如果无法创建最小线程数,进行清理 */
ThreadPoolShutdown(tp);
}
return retCode; // 返回结果代码,0 表示成功
}
CreateWorker函数:创建工作线程
/*!
* \brief Creates a worker thread, if the thread pool does not already have
* max threads.
*
* 创建一个工作线程,如果线程池中的线程数尚未达到最大线程数。
*
* \remark The ThreadPool object mutex must be locked prior to calling this
* function.
*
* 调用此函数之前,必须锁定 ThreadPool 对象的互斥锁(mutex)。
*
* \internal
*
* \return
* \li \c 0 on success, < 0 on failure.
* \li 成功返回 0,失败时返回小于 0 的值。
* \li \c EMAXTHREADS if already max threads reached.
* \li 如果已经达到最大线程数,返回 EMAXTHREADS。
* \li \c EAGAIN if system can not create thread.
* \li 如果系统无法创建新线程,返回 EAGAIN。
*/
static int CreateWorker(
/*! A pointer to the ThreadPool object. */
// 指向 ThreadPool 对象的指针。
ThreadPool *tp)
{
// 临时变量,用于存储新创建线程的句柄。
ithread_t temp;
// 返回代码初始化为 0,表示成功。
int rc = 0;
// 线程属性结构体,用于设置新线程的属性。
ithread_attr_t attr;
/* if a new worker is the process of starting, wait until it fully
* starts
*
* 如果一个新的工作线程正在启动过程中,等待它完全启动。
*/
while (tp->pendingWorkerThreadStart) {
// 等待条件变量,直到新的线程完成启动。
ithread_cond_wait(&tp->start_and_shutdown, &tp->mutex);
}
// 检查是否已达到最大线程数
if (tp->attr.maxThreads != INFINITE_THREADS &&
tp->totalThreads + 1 > tp->attr.maxThreads) {
// 如果线程数超过最大线程数,返回 EMAXTHREADS 错误。
return EMAXTHREADS;
}
// 初始化线程属性
ithread_attr_init(&attr);
// 设置线程栈大小为线程池中指定的大小。
ithread_attr_setstacksize(&attr, tp->attr.stackSize);
// 设置线程为分离状态(Detached),表示该线程结束后资源会自动释放。
ithread_attr_setdetachstate(&attr, ITHREAD_CREATE_DETACHED);
// 创建新线程,并将新线程与 WorkerThread 函数关联。线程属性通过 attr 设置,tp 是传递给新线程的参数。
rc = ithread_create(&temp, &attr, WorkerThread, tp);
// 销毁线程属性对象,释放资源。
ithread_attr_destroy(&attr);
// 如果线程创建成功
if (rc == 0) {
// 设置标志,表示新的工作线程正在启动。
tp->pendingWorkerThreadStart = 1;
// 等待新线程启动完成。创建的工作线程会在准备好后将线程池的标志置为0
while (tp->pendingWorkerThreadStart) {
ithread_cond_wait(&tp->start_and_shutdown, &tp->mutex);
}
}
// 更新统计信息,如果当前线程数超过之前的最大线程数,则更新最大线程数。
if (tp->stats.maxThreads < tp->totalThreads) {
tp->stats.maxThreads = tp->totalThreads;
}
// 返回创建线程的结果,0 表示成功,非 0 表示失败。
return rc;
}
WorkerThread
- 代码rc = ithread_create(&temp, &attr, WorkerThread, tp);会创建一个新的线程,并运行WorkerThread函数。

下面是对 WorkerThread 函数逐行的注释,解释其工作原理。
/*!
* \brief Implements a thread pool worker. Worker waits for a job to become
* available. Worker picks up persistent jobs first, high priority,
* med priority, then low priority.
*
* 实现线程池工作者线程的逻辑。工作者线程等待作业可用,优先处理持久性作业,
* 然后按优先级高、中、低的顺序处理其他作业。
*
* If worker remains idle for more than specified max, the worker is released.
*
* 如果工作者线程空闲时间超过指定的最大值,则该线程会被释放。
*
* \internal
*/
static void *WorkerThread(
/*! arg -> is cast to (ThreadPool *). */
// 参数 `arg` 会被转换为 `ThreadPool *` 类型
void *arg)
{
// 记录线程开始时间的变量
time_t start = 0;
// 指向线程池中的作业的指针,初始为 NULL
ThreadPoolJob *job = NULL;
// 列表中的节点,用于从作业队列中提取作业
ListNode *head = NULL;
// 用于超时等待的时间结构体
struct timespec timeout;
// 返回代码,初始化为 0
int retCode = 0;
// 表示是否是持久线程的标志
int persistent = -1;
// 将传入的参数 `arg` 转换为 `ThreadPool *`
ThreadPool *tp = (ThreadPool *)arg;
// 初始化线程库(根据平台的需要,linux可能是空操作)
ithread_initialize_thread();
/* Increment total thread count */
// 增加线程池中线程的总数,并通知其它等待线程该线程已启动
ithread_mutex_lock(&tp->mutex);
tp->totalThreads++;
tp->pendingWorkerThreadStart = 0;// 标记线程不再是待启动状态
// 广播信号,通知所有等待在条件变量 `start_and_shutdown` 上的线程
ithread_cond_broadcast(&tp->start_and_shutdown);
ithread_mutex_unlock(&tp->mutex);
// 设置随机种子,通常用于某些随机性需求的任务
SetSeed();
// 获取当前时间,用于计算线程的工作和空闲时间
StatsTime(&start);
while (1) {
// 获取线程池的互斥锁,确保对共享数据的安全访问
ithread_mutex_lock(&tp->mutex);
if (job) {
// 如果有作业在执行,作业完成后减少忙碌线程数,并释放作业
tp->busyThreads--;
FreeThreadPoolJob(tp, job);
job = NULL;
}
retCode = 0;
// 增加空闲线程数
tp->stats.idleThreads++;
// 更新总工作时间
tp->stats.totalWorkTime += (double)StatsTime(NULL) - (double)start;
// 重新获取当前时间,准备记录空闲时间
StatsTime(&start);
if (persistent == 0) {
// 如果当前线程不是持久线程,减少线程池中工作线程的计数
tp->stats.workerThreads--;
} else if (persistent == 1) {
// 如果是持久线程,则将其转为普通线程
tp->persistentThreads--;
}
/* Check for a job or shutdown */
// 如果所有优先级队列中都没有作业且没有持久作业,同时线程池未被关闭,则进入等待
while (tp->lowJobQ.size == 0 && tp->medJobQ.size == 0 &&
tp->highJobQ.size == 0 && !tp->persistentJob &&
!tp->shutdown) {
// 如果等待超时并且总线程数超过最小线程数,或者线程数超过最大线程数,释放当前线程
if ((retCode == ETIMEDOUT && tp->totalThreads > tp->attr.minThreads) ||
(tp->attr.maxThreads != -1 && tp->totalThreads > tp->attr.maxThreads)) {
tp->stats.idleThreads--;
goto exit_function;
}
// 设置相对超时时间
SetRelTimeout(&timeout, tp->attr.maxIdleTime);
/* wait for a job up to the specified max time */
// 在超时时间内等待新作业的到来
retCode = ithread_cond_timedwait(&tp->condition, &tp->mutex, &timeout);
}
// 减少空闲线程数
tp->stats.idleThreads--;
// 更新总空闲时间
tp->stats.totalIdleTime += (double)StatsTime(NULL) - (double)start;
// 重新获取当前时间,准备记录工作时间
StatsTime(&start);
// 提升饥饿作业的优先级
BumpPriority(tp);
// 如果线程池已关闭,退出循环
if (tp->shutdown) {
goto exit_function;
} else {
// 如果有持久作业,优先处理它
if (tp->persistentJob) {
job = tp->persistentJob;
tp->persistentJob = NULL;
tp->persistentThreads++;
persistent = 1;
ithread_cond_broadcast(&tp->start_and_shutdown);
} else {
// 没有持久作业时,从高优先级队列中取作业
tp->stats.workerThreads++;
persistent = 0;
// 依次尝试从高、中、低优先级队列中取作业
if (tp->highJobQ.size > 0) {
head = ListHead(&tp->highJobQ);
if (head == NULL) {
tp->stats.workerThreads--;
goto exit_function;
}
job = (ThreadPoolJob *)head->item;
CalcWaitTime(tp, HIGH_PRIORITY, job);
ListDelNode(&tp->highJobQ, head, 0);
} else if (tp->medJobQ.size > 0) {
head = ListHead(&tp->medJobQ);
if (head == NULL) {
tp->stats.workerThreads--;
goto exit_function;
}
job = (ThreadPoolJob *)head->item;
CalcWaitTime(tp, MED_PRIORITY, job);
ListDelNode(&tp->medJobQ, head, 0);
} else if (tp->lowJobQ.size > 0) {
head = ListHead(&tp->lowJobQ);
if (head == NULL) {
tp->stats.workerThreads--;
goto exit_function;
}
job = (ThreadPoolJob *)head->item;
CalcWaitTime(tp, LOW_PRIORITY, job);
ListDelNode(&tp->lowJobQ, head, 0);
} else {
// 理论上不应到达此处,若到达则表示出错
tp->stats.workerThreads--;
goto exit_function;
}
}
}
// 增加忙碌线程数
tp->busyThreads++;
// 释放互斥锁,允许其他线程访问共享资源
ithread_mutex_unlock(&tp->mutex);
/* In the future can log info */
// 设置作业的优先级(如果需要)
if (SetPriority(job->priority) != 0) {
// 处理设置优先级失败的情况
} else {
// 成功设置优先级后的逻辑
}
// 执行作业
job->func(job->arg);
// 恢复为默认优先级
SetPriority(DEFAULT_PRIORITY);
}
exit_function:
// 减少线程池中的总线程数,并通知其他等待的线程
tp->totalThreads--;
ithread_cond_broadcast(&tp->start_and_shutdown);
// 释放互斥锁
ithread_mutex_unlock(&tp->mutex);
// 清理线程的资源
ithread_cleanup_thread();
return NULL;
}
-
功能:该函数实现了线程池中的工作线程逻辑。工作线程等待作业到来,优先处理持久作业,并按照优先级从高到低处理普通作业。如果线程池空闲时间过长或线程数超过限制,工作线程会自行销毁。
-
线程同步:通过互斥锁和条件变量实现线程间的同步,确保线程安全地访问共享资源。
-
优先级处理:线程首先处理持久作业,然后根据优先级依次处理高、中、低优先级的作业。
-
TPJobInit(&job, (start_routine)AutoAdvertise, adEvent);可添加JOB,以下是WorkerThread对JOB的执行。


相关任务队列
- 相关任务队列如下,与任务优先级相关的两个关键函数为SetPriority和BumpPriority:

1. 任务的空闲列表(Free List of Jobs)
- 当任务执行完成后,任务对象会被回收到空闲列表中。
- 当线程池需要分配新任务时,如果空闲列表中存在可用的任务对象,则直接从空闲列表中取出并复用,而不需要重新创建任务对象。
- 这样可以提高线程池的效率,尤其在大量短任务需要频繁创建和销毁时。
2. 低优先级任务队列(Low Priority Job Queue)
- 任务调度器会根据任务的优先级,将低优先级任务放入低优先级任务队列中。
- 当所有高优先级和中等优先级的任务都处理完,且线程池中有空闲线程时,低优先级任务才会被分配给线程执行。
3. 中等优先级任务队列(Medium Priority Job Queue)
- 中等优先级任务队列存储需要尽快执行的任务。
- 在调度时,线程池会先处理高优先级任务,之后再处理中等优先级任务。
- 如果线程池有可用的线程,并且没有高优先级任务等待,中等优先级任务会被调度执行。
4. 高优先级任务队列(High Priority Job Queue)
- 高优先级任务在提交到线程池后,会被放入高优先级任务队列中。
- 当线程池有空闲线程时,调度器会首先查看高优先级任务队列,并分配空闲线程来执行这些任务。
- 高优先级任务可以抢占正在处理的低优先级或中等优先级任务。
5. 持久任务(Persistent Job)
- 持久任务是一些需要持续运行的任务,通常不会被移除线程池,除非系统关闭或特殊条件触发。
- 持久任务通常会在特定线程上执行,这些线程不会在空闲时销毁,而是始终保持活跃。
- 持久任务在调度中可能具有较高的优先级,且通常绑定到特定线程上持续运行,不会被其他任务抢占。
3149

被折叠的 条评论
为什么被折叠?



