书圈|看完这篇还不懂高并发中的线程与线程池你来打我(内含20张图)( 四 )


文章图片
这种场景非常适合创建专用的线程来处理某些特定任务 , 这种情况比较简单 。
有长任务 , 相应的就有短任务 。
2 , 短任务 , short-livedtasks
这个概念也很简单 , 那就是任务的处理时间很短 , 比如一次网络请求、一次数据库查询等 , 这种任务可以在短时间内快速处理完成 。 因此短任务多见于各种Server , 像webserver、databaseserver、fileserver、mailserver等 , 这也是互联网行业的同学最常见的场景 , 这种场景是我们要重点讨论的 。
这种场景有两个特点:一个是任务处理所需时间短;另一个是任务数量巨大 。
如果让你来处理这种类型的任务该怎么办呢?
你可能会想 , 这很简单啊 , 当server接收到一个请求后就创建一个线程来处理任务 , 处理完成后销毁该线程即可 , Soeasy 。
这种方法通常被称为thread-per-request , 也就是说来一个请求就创建一个线程:
书圈|看完这篇还不懂高并发中的线程与线程池你来打我(内含20张图)
文章图片
如果是长任务 , 那么这种方法可以工作的很好 , 但是对于大量的短任务这种方法虽然实现简单但是有这样几个缺点:
1.从前几节我们能看到 , 线程是操作系统中的概念(这里不讨论用户态线程实现、协程之类) , 因此创建线程天然需要借助操作系统来完成 , 操作系统创建和销毁线程是需要消耗时间的
2.每个线程需要有自己独立的栈 , 因此当创建大量线程时会消耗过多的内存等系统资源
这就好比你是一个工厂老板(想想都很开心有没有) , 手里有很多订单 , 每来一批订单就要招一批工人 , 生产的产品非常简单 , 工人们很快就能处理完 , 处理完这批订单后就把这些千辛万苦招过来的工人辞退掉 , 当有新的订单时你再千辛万苦的招一遍工人 , 干活儿5分钟招人10小时 , 如果你不是励志要让企业倒闭的话大概是不会这么做到的 , 因此一个更好的策略就是招一批人后就地养着 , 有订单时处理订单 , 没有订单时大家可以闲呆着 。
书圈|看完这篇还不懂高并发中的线程与线程池你来打我(内含20张图)
文章图片
这就是线程池的由来 。
从多线程到线程池
线程池的概念是非常简单的 , 无非就是创建一批线程 , 之后就不再释放了 , 有任务就提交给这些线程处理 , 因此无需频繁的创建、销毁线程 , 同时由于线程池中的线程个数通常是固定的 , 也不会消耗过多的内存 , 因此这里的思想就是复用、可控 。
线程池是如何工作的
可能有的同学会问 , 该怎么给线程池提交任务呢?这些任务又是怎么给到线程池中线程呢?
很显然 , 数据结构中的队列天然适合这种场景 , 提交任务的就是生产者 , 消费任务的线程就是消费者 , 实际上这就是经典的生产者-消费者问题 。
书圈|看完这篇还不懂高并发中的线程与线程池你来打我(内含20张图)
文章图片
现在你应该知道为什么操作系统课程要讲、面试要问这个问题了吧 , 因为如果你对生产者-消费者问题不理解的话 , 本质上你是无法正确的写出线程池的 。
限于篇幅在这里博主不打算详细的讲解生产者消费者问题 , 参考操作系统相关资料就能获取答案 。 这里博主打算讲一讲一般提交给线程池的任务是什么样子的 。
一般来说提交给线程池的任务包含两部分:1)需要被处理的数据;2)处理数据的函数
structtask{void*data;//任务所携带的数据handlerhandle;//处理数据的方法}(注意 , 你也可以把代码中的struct理解成class , 也就是对象 。 )
线程池中的线程会阻塞在队列上 , 当生产者向队列中写入数据后 , 线程池中的某个线程会被唤醒 , 该线程从队列中取出上述结构体(或者对象) , 以结构体(或者对象)中的数据为参数并调用处理函数:


推荐阅读