线程池C++

合集下载

线程池的原理

线程池的原理

线程池的原理
线程池是一种并发处理机制,在程序启动时创建一定数量的线程,并且维护一个任务队列。

当有任务需要处理时,线程池中的线程会从任务队列中取出任务进行处理。

线程池的原理如下:
1. 创建线程池:在程序初始化时,创建一定数量的线程,并且将它们置于等待状态,等待任务的到来。

2. 添加任务:当有任务需要处理时,将任务添加到任务队列中。

3. 任务分配:线程池中的线程会不断地从任务队列中取出任务进行处理,直到任务队列为空。

每个线程只能处理一个任务,处理完后会再次进入等待状态。

4. 线程复用:当一个线程处理完一个任务后,可以立即处理下一个任务,而不需要销毁和重新创建线程,从而减少了线程创建和销毁的开销。

5. 线程管理:线程池管理线程的数量,根据实际需要动态调整线程的数量。

可以根据线程池的策略,动态增加或减少线程的数量。

6. 控制并发:线程池可以控制并发的数量,防止因为任务过多导致系统内存溢出或者性能下降。

7. 错误处理:线程池中的线程处理任务时可能会产生异常,需要对异常进行处理,防止线程因为异常退出而导致整个线程池无法正常工作。

通过使用线程池,我们可以更好地管理线程,提高程序的性能和可靠性。

c语言 多线程下,动态加载配置的方法

c语言 多线程下,动态加载配置的方法

c语言多线程下,动态加载配置的方法一、引言在C语言的多线程环境下,动态加载配置是一项重要的技术。

配置文件通常包含应用程序的运行参数,而多线程环境下的配置加载可以提高程序的灵活性和可扩展性。

本文将介绍如何在C语言的多线程环境下,动态加载配置的方法。

二、动态加载配置的概念动态加载配置是指根据应用程序的运行环境,动态地加载配置文件。

这样,当应用程序启动时,可以根据需要选择加载的配置文件,并且可以随着应用程序的运行环境变化而实时更新配置。

三、多线程环境下的配置加载方法1. 创建线程池:首先,我们需要创建一个线程池,用于管理多个线程。

线程池可以有效地利用系统资源,提高程序的性能和稳定性。

2. 配置文件路径:在多线程环境下,需要确保配置文件的路径正确,以便线程可以找到并加载配置文件。

通常,可以将配置文件的路径作为环境变量传递给每个线程。

3. 动态加载配置:在每个线程中,可以使用C语言的动态库加载功能,动态加载配置文件。

这样可以确保在多线程环境下,每个线程都有自己的配置文件,避免数据冲突和同步问题。

4. 配置解析:加载完配置文件后,需要对配置文件进行解析。

可以使用C语言中的字符串处理函数和数据结构,将配置文件中的数据解析为应用程序所需的数据结构。

5. 共享数据:虽然每个线程有自己的配置文件,但是需要确保各个线程之间可以共享一些必要的数据。

可以通过将共享数据存储在全局变量或使用同步机制来实现。

四、注意事项1. 并发安全性:在多线程环境下,需要注意并发安全性问题。

例如,需要避免多个线程同时修改共享数据导致的数据冲突和错误。

可以使用锁或其他同步机制来确保数据的一致性。

2. 错误处理:在动态加载配置的过程中,需要做好错误处理。

例如,当无法加载配置文件或解析错误时,需要及时报告错误并采取相应的措施。

3. 性能优化:在多线程环境下,需要考虑性能优化问题。

可以通过使用合适的算法和数据结构,以及合理地管理线程资源来提高程序的性能。

线程池的参数和理解

线程池的参数和理解

线程池的参数和理解线程池是一种用于管理和重复使用线程的机制,它可以提高多线程应用程序的性能和效率。

线程池通常具有一些参数,以控制线程的数量、生命周期和行为。

以下是线程池的一些常见参数和它们的理解:1.核心线程数(Core Pool Size):这是线程池中保持活动的最小线程数。

即使没有任务要执行,核心线程也会一直存活。

核心线程通常用于处理短期和频繁到来的任务。

2.最大线程数(Maximum Pool Size):这是线程池允许的最大线程数量。

当任务数量超过核心线程数时,线程池会创建新的线程,但不会超过最大线程数。

如果任务量继续增加,线程池可能会拒绝接受更多的任务。

3.任务队列(Task Queue):当线程池中的线程都在处理任务时,新任务将被放入任务队列中等待执行。

任务队列可以是有界的(固定大小)或无界的(不限制大小),具体取决于线程池的配置。

4.线程存活时间(Thread Keep-Alive Time):当线程池中的线程数量超过核心线程数,并且处于空闲状态时,它们的存活时间。

如果在存活时间内没有新任务分配给这些线程,它们将被终止并从线程池中移除。

5.拒绝策略(Rejection Policy):当任务队列已满且线程池中的线程达到最大数时,新任务的处理方式。

常见的拒绝策略包括抛出异常、丢弃任务、将任务交给调用线程执行或将任务重新放入队列等。

6.线程工厂(Thread Factory):用于创建线程的工厂。

线程池通过线程工厂创建线程,并为每个线程分配唯一的名称和其他属性。

理解这些参数有助于配置适合您应用需求的线程池。

核心线程数和最大线程数影响了线程池的大小,任务队列决定了任务的排队方式,线程存活时间影响了线程的生命周期,而拒绝策略决定了线程池在负载过重时的行为。

适当地调整这些参数可以提高多线程应用的性能,并确保线程池在不同工作负载下能够有效运行。

C#多线程中等待线程池中的所有线程执行完毕后再执行下一个线程

C#多线程中等待线程池中的所有线程执行完毕后再执行下一个线程

C#多线程中等待线程池中的所有线程执⾏完毕后再执⾏下⼀个线程⽹上找的,做个笔记记录⼀下。

有这么⼀个需求,就是巡检多台服务器是否都在线,点击巡检按钮后,按⾏读取DataGridView中的数据,并启⾏线程执⾏,这时会存在多个线程同时运⾏,但是什么时候给出⽤户提醒,说都巡检完成了呢,需要⽤到⼀个线程状态的检测。

最后的效果是这样⼦的,多个线程对表格按⾏进⾏服务器的巡检,只有等所有的巡检线都结束后,等待线程才会弹出⼀个巡检完毕的提⽰框,在巡检的过程中,不会卡主界⾯。

1、新建⼀个类,⽤于处理线程状态的计数,⽤于解决EventWaitHandle时线程不能超过64个的问题public class MutipleThreadResetEvent : IDisposable{private readonly ManualResetEvent done;private readonly int total;private long current;///<summary>///构造函数///</summary>///<param name="total">需要等待执⾏的线程总数</param>public MutipleThreadResetEvent(int total){this.total = total;current = total;done = new ManualResetEvent(false);}///<summary>///唤醒⼀个等待的线程///</summary>public void SetOne(){// Interlocked 原⼦操作类 ,此处将计数器减1if (Interlocked.Decrement(ref current) == 0){//当所以等待线程执⾏完毕时,唤醒等待的线程done.Set();}}///<summary>///等待所以线程执⾏完毕///</summary>public void WaitAll(){done.WaitOne();}///<summary>///释放对象占⽤的空间///</summary>public void Dispose(){((IDisposable)done).Dispose();}}2、定义⼀个结构体,⽤于存放线程需要的参数和MutipleThreadResetEventstruct webInfo{public int xh;public string lx;public string path;public object mre;//public ManualResetEvent mre;}3、在DataGridView中使⽤int num = DG_show.RowCount - 1;for (int i = 0; i < DG_show.RowCount - 1; i++){DG_show.Rows[i].Cells["验证结果"].Value = "";DG_show.Rows[i].DefaultCellStyle.BackColor = Color.White;}//开始进⾏验证//manualEvents.Clear(); //以前的代码,可以删除PR1.Maximum = num;PR1.Minimum = 0;var countdown = new MutipleThreadResetEvent(num);webInfo info1;ThreadPool.SetMaxThreads(5,5); //设置最⼤的线程数量for (int i = 0; i < DG_show.RowCount - 1; i++){//ManualResetEvent mre = new ManualResetEvent(false);//manualEvents.Add(mre);info1.xh = i;//info1.mre = mre;info1.mre = countdown;info1.lx = DG_show.Rows[i].Cells["⽅式"].Value.ToString().Trim();info1.path = DG_show.Rows[i].Cells["地址"].Value.ToString().Trim();//Thread thread1 = new Thread(new ParameterizedThreadStart(CheckResult));//thread1.Start(info1); //进⾏巡检的线程ThreadPool.QueueUserWorkItem(CheckResult, info1);}//等待所有巡检线程执⾏完毕的线程Thread th1 = new Thread(new ParameterizedThreadStart(WaitThreadEnd));th1.Start(countdown);CheckResult和WaitThreadEnd就是具体的业务处理4、巡检线程和等待线程中的写法巡检线程private void CheckResult(object info){webInfo web = (webInfo)info;//... ...省略掉具体的业务过程MutipleThreadResetEvent countdown = web.mre as MutipleThreadResetEvent;countdown.SetOne();}等待巡检线程执⾏完毕的等待线程,其中执⾏⼀些启⽤主ui界⾯控件显⽰,进度条控件,按钮控件等操作,但是弹出的对话框有点问题,是在线程中弹出的,⽤户可能关注不到它。

c++ 线程池面试题

c++ 线程池面试题

线程池是一个用于管理线程的容器,它可以预先创建一组线程并保存在内存中,以避免频繁地创建和销毁线程。

以下是一些可能的C++线程池面试题:
1.什么是线程池?它的主要作用是什么?
2.线程池的主要组成部分有哪些?
3.如何实现一个简单的线程池?
4.线程池如何处理任务的调度和执行?
5.线程池如何管理线程的生命周期?
6.线程池如何处理任务的优先级和调度策略?
7.线程池如何处理任务的超时和取消?
8.线程池如何扩展和缩减大小?
9.线程池如何进行性能优化和调优?
10.线程池在实际应用中需要注意哪些问题?
这些问题可以帮助你了解应聘者对线程池的理解和实现经验,以及他们在实际应用中如何解决遇到的问题。

c++ 线程池的工作原理

c++ 线程池的工作原理

c++ 线程池的工作原理
线程池是一种用于管理和控制执行多个线程的机制。

它包括一个工作队列,该队列用于保存任务的列表,并且有一组已经准备就绪的线程可以执行这些任务。

线程池的工作原理如下:
1. 创建线程池:首先要创建线程池,这个过程中需要指定线程池的大小,最大任务队列长度以及线程池的优先级等参数。

2. 将任务添加到任务队列:当一个任务需要执行时,它会被添加到任务队列中等待执行。

3. 分配任务给线程:线程池中的线程会从任务队列中取出任务并执行。

当一个线程处于空闲状态时,它会从任务队列中获取一个任务并执行。

4. 执行任务:线程会执行任务,当任务完成时,线程会从任务队列中获取另一个任务并执行,直到所有任务都被执行完或线程池被关闭。

5. 关闭线程池:当线程池不再需要时,它会接收到关闭信号,并关闭线程池中所有线程,释放资源。

总之,线程池通过对任务队列的管理来有效地控制线程数量和执行顺序,从而提高了应用程序的性能和可靠性。

c++ threadpool 使用例程

C++ Threadpool 使用例程随着多核处理器的普及和计算机系统的发展,多线程编程已经成为了必备的技能。

C++ 作为一种十分强大和灵活的编程语言,提供了丰富的多线程库和工具,其中就包括了 Threadpool。

本文将为大家介绍 C++ Threadpool 的使用例程,帮助大家更好地理解和应用 Threadpool。

一、Threadpool 简介Threadpool 是一种用于管理多线程的技术。

它通过创建一组线程并将任务分配给这些线程来提高程序的并发性和效率。

Threadpool 通常包括以下几个组成部分:1. 任务队列:用于存储待执行的任务。

2. 线程池:包含若干个线程,用于执行任务队列中的任务。

3. 管理器:用于管理任务队列和线程池,并根据需要动态调整线程数量。

二、C++ Threadpool 的实现C++11 标准引入了新的线程库,其中就包括了 std::thread 和std::async 用于创建和管理线程。

在这个基础上,我们可以很方便地实现自己的 Threadpool。

具体实现步骤如下:1. 定义任务类```cppclass Task {public:virtual void run() = 0;virtual ~Task() {}};```2. 定义线程池类```cppclass ThreadPool {public:ThreadPool(size_t numThreads) {for (size_t i = 0; i < numThreads; ++i) {threads.push_back(std::thread([this] {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(queueMutex); condition.w本人t(lock, [this] { return stop|| !tasks.empty(); });if (stop tasks.empty()) {return;}task = std::move(tasks.front());tasks.pop();}task();}}));}}template<class F>void enqueue(F f) {{std::unique_lock<std::mutex> lock(queueMutex); tasks.push(std::function<void()>(f));}condition.notify_one();}~ThreadPool() {{std::unique_lock<std::mutex> lock(queueMutex);stop = true;}condition.notify_all();for (std::thread thread : threads) {thread.join();}}private:std::vector<std::thread> threads;std::queue<std::function<void()>> tasks;std::mutex queueMutex;std::condition_variable condition;bool stop = false;};```以上代码定义了一个基本的 Threadpool 类,包括了任务队列、线程池和管理器。

C#线程篇---Task(任务)和线程池不得不说的秘密(5)

C#线程篇---Task(任务)和线程池不得不说的秘密(5)在上篇最后⼀个例⼦之后,我们发现了怎么去使⽤线程池,调⽤ThreadPool的QueueUserWorkItem⽅法来发起⼀次异步的、计算限制的操作,例⼦很简单,不是吗? 然⽽,在今天这篇博客中,我们要知道的是,QueueUserWorkItem这个技术存在许多限制。

其中最⼤的问题是没有⼀个内建的机制让你知道操作在什么时候完成,也没有⼀个机制在操作完成是获得⼀个返回值,这些问题使得我们都不敢启⽤这个技术。

Microsoft为了克服这些限制(同时解决其他⼀些问题),引⼊了任务(tasks)的概念。

顺带说⼀下我们得通过System.Threading.Tasks命名空间来使⽤它们。

现在我要说的是,⽤线程池不是调⽤ThreadPool的QueueUserWorkItem⽅法,⽽是⽤任务来做相同的事:1 static void Main(string[] args)2 {3 Console.WriteLine("主线程启动");4 //ThreadPool.QueueUserWorkItem(StartCode,5);5 new Task(StartCode, 5).Start();6 Console.WriteLine("主线程运⾏到此!");7 Thread.Sleep(1000);8 }910 private static void StartCode(object i)11 {12 Console.WriteLine("开始执⾏⼦线程...{0}",i);13 Thread.Sleep(1000);//模拟代码操作14 }15 }嘿,你会发现结果是⼀样的。

再来看看这个是什么:TaskCreationOptions这个类型是⼀个枚举类型,传递⼀些标志来控制Task的执⾏⽅式。

线程池的corepoolsize及maximumpoolsize创建原理

线程池的corepoolsize及maximumpoolsize创建原理线程池是一种常用的多线程处理机制,可以有效地管理和复用线程资源,提高程序的性能和效率。

线程池有两个重要参数:corePoolSize(核心线程数)和maximumPoolSize(最大线程数),它们的创建原理如下:1. corePoolSize(核心线程数):corePoolSize 是线程池中核心线程的数量。

当有任务提交到线程池时,如果当前核心线程数小于corePoolSize,线程池就会创建一个新的线程来处理任务。

如果当前核心线程数已达到corePoolSize,新的任务将会被放入任务队列等待执行。

核心线程会一直存在,即使没有任务需要执行。

2. maximumPoolSize(最大线程数):maximumPoolSize 是线程池中允许的最大线程数。

当任务提交到线程池时,如果当前核心线程数小于corePoolSize,线程池会创建新的线程来处理任务,直到达到corePoolSize。

如果当前核心线程数已经达到corePoolSize,而任务队列已满,线程池会继续创建新的线程,直到达到maximumPoolSize。

超过maximumPoolSize 的任务将由饱和策略进行处理,默认的饱和策略是抛出异常。

线程池的创建原理主要是根据提交的任务数量和线程池的状态来决定是否创建新的线程,以及创建线程的数量。

通过合理地设置corePoolSize 和maximumPoolSize,可以根据任务的特性和系统的负载来优化线程池的性能。

需要注意的是,合理设置corePoolSize 和maximumPoolSize 是一项重要的工作,不当的设置可能导致线程过多或过少,从而影响系统的性能和资源利用率。

在实际应用中,应根据具体需求和系统负载进行调整和优化。

C#异步和多线程以及THREAD、THREADPOOL、TASK区别和使用方法

C#异步和多线程以及THREAD、THREADPOOL、TASK区别和使⽤⽅法本⽂的⽬的是为了让⼤家了解什么是异步?什么是多线程?如何实现多线程?对于当前C#当中三种实现多线程的⽅法如何实现和使⽤?什么情景下选⽤哪⼀技术更好?第⼀部分主要介绍在C#中异步(async/await)和多线程的区别,以及async/await使⽤⽅法。

第⼆部分主要介绍在C#多线程当中Thread、ThreadPool、Task区别和使⽤⽅法。

-------------------------------------------------------------------------------------------------------------------------async/await这⾥的异步只是⼀种编程模式,⼀个编程接⼝设计为异步的,⼤多数时候都是为了灵活地处理并发流程需求的,对于async/await⽤法请看以下代码:static void Main(string[] args){_ = Async1();Console.WriteLine("...............按任意键退出");Console.ReadKey();}static async Task Async1(){Console.WriteLine("异步开始");var r = await Async2();var x = await Async3(r);Console.WriteLine("结果是 {0}", r + x);}static async Task<int> Async2(){await Task.Delay(1000);//⼀种异步延迟⽅法return 100;}static async Task<int> Async3(int x){await Task.Delay(1000);return x % 7;}执⾏结果:使⽤async关键字修饰的⽅法为异步⽅法,async关键字要和await关键字⼀同使⽤才会⽣效。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

C语言实现简单线程池有时我们会需要大量线程来处理一些相互独立的任务,为了避免频繁的申请释放线程所带来的开销,我们可以使用线程池。

下面是一个C语言实现的简单的线程池。

头文件:1:#ifndef THREAD_POOL_H__2:#define THREAD_POOL_H__3:4:#include <pthread.h>5:6:/* 要执行的任务链表 */7:typedef struct tpool_work {8:void* (*routine)(void*); /* 任务函数 */ 9:void *arg; /* 传入任务函数的参数 */10:struct tpool_work *next;11: }tpool_work_t;12:13:typedef struct tpool {14:int shutdown; /* 线程池是否销毁 */15:int max_thr_num; /* 最大线程数*/16: pthread_t *thr_id; /* 线程ID数组*/17: tpool_work_t *queue_head; /* 线程链表 */ 18: pthread_mutex_t queue_lock;19: pthread_cond_t queue_ready;20: }tpool_t;21:22:/*23: * @brief 创建线程池24: * @param max_thr_num 最大线程数25: * @return 0: 成功其他: 失败26: */27:int28: tpool_create(int max_thr_num);29:30:/*31: * @brief 销毁线程池32: */33:void34: tpool_destroy();35:36:/*37: * @brief 向线程池中添加任务38: * @param routine 任务函数指针39: * @param arg 任务函数参数40: * @return 0: 成功其他:失败41: */42:int43: tpool_add_work(void*(*routine)(void*), void *arg);44:45:#endif实现:1:#include <unistd.h>2:#include <stdlib.h>3:#include <errno.h>4:#include <string.h>5:#include <stdio.h>6:7:#include"tpool.h"8:9:static tpool_t *tpool = NULL;10:11:/* 工作者线程函数, 从任务链表中取出任务并执行 */12:static void*13: thread_routine(void *arg)14: {15: tpool_work_t *work;16:17:while(1) {18:/* 如果线程池没有被销毁且没有任务要执行,则等待 */ 19: pthread_mutex_lock(&tpool->queue_lock);20:while(!tpool->queue_head && !tpool->shutdown) { 21: pthread_cond_wait(&tpool->queue_ready,&tpool->queue_lock);22: }23:if (tpool->shutdown) {24: pthread_mutex_unlock(&tpool->queue_lock);25: pthread_exit(NULL);26: }27: work = tpool->queue_head;28: tpool->queue_head = tpool->queue_head->next;29: pthread_mutex_unlock(&tpool->queue_lock);30:31: work->routine(work->arg);32: free(work);33: }34:35:return NULL;36: }37:38:/*39: * 创建线程池40: */41:int42: tpool_create(int max_thr_num)43: {44:int i;45:46: tpool = calloc(1, sizeof(tpool_t));47:if (!tpool) {48: printf("%s: calloc failed\n", __FUNCTION__);49: exit(1);50: }51:52:/* 初始化 */53: tpool->max_thr_num = max_thr_num;54: tpool->shutdown = 0;55: tpool->queue_head = NULL;56:if (pthread_mutex_init(&tpool->queue_lock, NULL) !=0) { 57: printf("%s: pthread_mutex_init failed, errno:%d, error:%s\n",58: __FUNCTION__, errno, strerror(errno));59: exit(1);60: }61:if (pthread_cond_init(&tpool->queue_ready, NULL) !=0 ) { 62: printf("%s: pthread_cond_init failed, errno:%d, error:%s\n",63: __FUNCTION__, errno, strerror(errno));64: exit(1);65: }66:67:/* 创建工作者线程 */68: tpool->thr_id = calloc(max_thr_num, sizeof(pthread_t)); 69:if (!tpool->thr_id) {70: printf("%s: calloc failed\n", __FUNCTION__);71: exit(1);72: }73:for (i = 0; i < max_thr_num; ++i) {74:if (pthread_create(&tpool->thr_id[i], NULL,thread_routine, NULL) != 0){75: printf("%s:pthread_create failed, errno:%d, error:%s\n", __FUNCTION__,76: errno, strerror(errno));77: exit(1);78: }79:80: }81:82:return 0;83: }84:85:/* 销毁线程池 */86:void87: tpool_destroy()88: {89:int i;90: tpool_work_t *member;91:92:if (tpool->shutdown) {93:return;94: }95: tpool->shutdown = 1;96:97:/* 通知所有正在等待的线程 */98: pthread_mutex_lock(&tpool->queue_lock);99: pthread_cond_broadcast(&tpool->queue_ready);100: pthread_mutex_unlock(&tpool->queue_lock);101:for (i = 0; i < tpool->max_thr_num; ++i) {102: pthread_join(tpool->thr_id[i], NULL);103: }104: free(tpool->thr_id);105:106:while(tpool->queue_head) {107: member = tpool->queue_head;108: tpool->queue_head = tpool->queue_head->next;109: free(member);110: }111:112: pthread_mutex_destroy(&tpool->queue_lock);113: pthread_cond_destroy(&tpool->queue_ready);114:115: free(tpool);116: }117:118:/* 向线程池添加任务 */119:int120: tpool_add_work(void*(*routine)(void*), void *arg)121: {122: tpool_work_t *work, *member;123:124:if (!routine){125: printf("%s:Invalid argument\n", __FUNCTION__); 126:return -1;127: }128:129: work = malloc(sizeof(tpool_work_t));130:if (!work) {131: printf("%s:malloc failed\n", __FUNCTION__);132:return -1;133: }134: work->routine = routine;135: work->arg = arg;136: work->next = NULL;137:138: pthread_mutex_lock(&tpool->queue_lock);139: member = tpool->queue_head;140:if (!member) {141: tpool->queue_head = work;142: } else {143:while(member->next) {144: member = member->next;145: }146: member->next = work;147: }148:/* 通知工作者线程,有新任务添加 */149: pthread_cond_signal(&tpool->queue_ready);150: pthread_mutex_unlock(&tpool->queue_lock);151:152:return 0;153: }154:155:测试代码:1:#include <unistd.h>2:#include <stdio.h>3:#include <stdlib.h>4:#include"tpool.h"5:6:void *func(void *arg)7: {8: printf("thread %d\n", (int)arg);9:return NULL;10: }11:12:int13: main(int arg, char **argv)14: {15:if (tpool_create(5) != 0) {16: printf("tpool_create failed\n");17: exit(1);18: }19:20:int i;21:for (i = 0; i < 10; ++i) {22: tpool_add_work(func, (void*)i);23: }24: sleep(2);25: tpool_destroy();26:return 0;27: }这个实现是在调用tpool_destroy之后,仅将当前正在执行的任务完成之后就会退出,我们也可以修改代码使得线程池在执行完任务链表中所有任务后再退出。

相关文档
最新文档