9、多线程实战之技巧篇

  • (分而治之思想)将一批任务分进行分解并指派到个人的过程就是一个分而治之的过程。进一步利用多线程,将一个任务分解为若干个子任务并指派专门的线程来负责执行这些子任务。
  • 要使用分而治之的思想,首先需要将程序算法中只能串行执行和可以并发执行的部分区分开来,然后使用专门的线程去并发执行可以并发的部分。多线程中分而治之的两种方式:基于数据的分割基于任务的分割。前者从数据入手,将程序的输入数据分解为若干个规模较小的数据,并利用若干个工作线程并发处理这些分解后的数据;后者从程序的处理任务(步骤入手)将任务分解为若干子任务,并分配若干子线程并发执行这些子任务。

一、基于数据分割实现并发化

  • 如果原始输入数据规模非常的大,比如从几百万条日志记录中统计出我们所需要的信息,就可以使用数据分割来实现。基本思想是:将原始数据按照一定的规则分解为若干规模较小的子输入数据,并使用工作线程来讲这些子输入进行处理,从而实现对输入数据的并发处理。因此基于数据分割的结果是产生一批子任务,这些子任务由专门的工作线程去执行。
  • 以下载大文件为例,思路就是:先获取待下载资源的大小(通过http 中的head的字段content-length获取),在根据多线程的个数来决定子任务的个数,然后确定每一个子任务负责下载文件的数据段范围,最后分别创建相应的下载子任务并为每一个下载任务创建相应的下载线程。源码参考:后续补

需要注意的地方:
1、工作者线程数量的合理设置问题
2、工作者线程异常处理问题(重试、快速失败等)
3、原始数据未知问题(可以采用批处理的方式对原始数据进行分解,聚集了一批数据后再分配给工作线程去执行,但是需要考虑指派给线程的负载均衡问题)
4、程序的复杂性增加问题

二、基于任务的分割实现并行化

  • 使用过个线程去共同完成一个任务的执行,就是基于任务的分割。基本思想:将原始任务按照一定的规则分解成若干任务,并使用专门的工作线程去执行这些子任务,从而实现任务的并发执行。
  • 按照原始任务的分解方式划分,基于任务的分解可以分为按照任务的资源消耗属性分割和处理步骤分割两种。
1、按照资源的消耗属性分割
  • 线程执行的任务按照其消耗的主要资源可以划分为CPU密集型和IO密集型任务。相应的执行这些任务的线程称为CPU密集型线程(典型案例加密和解密)和IO密集型线程(典型案例文件读写、网络读写)。一个线程如果同时兼具CPU密集型任务和IO密集型任务,称为混合型任务。
  • 以分布式系统中,需要一个统计工具,用于从指定的接口日志文件中统计出外部系统处理指定请求的相应延时情况。日志格式和处理算法如下:


    日志格式

    处理算法

分析:

  • 如果使用之前说的按数据切分的方法,比如400个日志文件,使用4个线程同时去执行(1)同一个请求的开始和结束可能在不同的日志文件中,因此存在线程安全问题,导致处理复杂度上升(2)可能导致io资源争用而减低io效率(3)可能导致处理器时间的浪费,一个工作线程在等待磁盘返回数据期间是处于waiting状态的。
  • 如果使用单线程可以避免(1)问题和(2)问题,但是同样会出现(3)问题,同时处理过程慢
  • 由于日志文件的读取操作是io密集型的而关于时间的统计是cpu密集型的,因此可以考虑采用两种线程配合去执行。LogReaderThread负责日志文件的读取采集;然后在启动一个线程负责数据统计。
  • 参考源码:待补充
  • 总结:基于任务的分割结果是产生多个相互协作的异质工作者线程

(1)基于任务的分割可能导致程序的复杂度增加
(2)多线程可能导致而外的时间消耗
(3)多线程未必比相应的单线程快
(4)考虑从单线程向多线程进化

2、按处理步骤分割

三、合理设置线程数

线程设置过小的话,可能导致无法充分利用处理器资源;线程也不宜过大,过大的话会增加线程上下文的开销以及其他开销。

1、Amdahl's定律
2、设置线程数
image.png

你可能感兴趣的:(9、多线程实战之技巧篇)