【p2p、分布式,区块链笔记 UPNP】: Libupnp test_init.c 03 初始化SDK --- 线程池初始化(UpnpInitThreadPools)

  • 线程池初始化相关代码声明在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.csdn.net/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的执行。
    在这里插入图片描述
    在这里插入图片描述

相关任务队列

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值