并发编程-多线程的好处

上一文:并发编程-并发的简史

如果线程使用得当,多线程可以降低你的开发和维护成本,而且还能改善复杂应用程序的性能。多线程让模仿人类工作方式以及交互变得简单,多线程通过把异步的工作流转换成一个个的串行工作流。他们也可以把其它错综复杂(convoluted)的代码转换成简单明了的犹如一条直线般(straight line code)简单,使得代码容易编写、容易阅读、容易维护。(ps:与一条直线相对的就是一团乱麻,你想像一下你拿到一团乱麻要找到头绪时候的样子)

多线程在GUI(图形用户界面)application上是非常有用的。这个时候多线程可以帮助你改善与用户的响应性(responsiveness)。在server application上,可以改善资源的利用以及吞吐量(throughput)。多线程也可以简化JVM的实现,垃圾回收器(garbage collector)通常是运行在一个或多个专门的线程上。大多数比较牛逼的java application都一定程度上用到了多线程。(ps:如果你只是对代码浅尝辄止,那么以后有人问你,多线程都用到哪些地方?你就可以上这几点,gui、gc、server。)

1.2.1.Exploiting Multiple Processors 充分利用多个处理器

过去多处理器系统很昂贵也很稀少。只有那些很大的数据中心以及科学计算场所才有这种多处理器系统。今天,多处理器系统就比较便宜了,而且变得稀松平常;甚至一些低端的server以及中档的桌面系统都已经是多处理器了。这个趋势还在不断的加速;因为通过提高时钟速度已经变得比较难了,所以处理器厂商们就开始把多个处理器的core放到一个芯片(chip)上。现在几乎所有的主流的芯片制造商都已经开始了这种转变,而且我们已经看到了很多机器上都已经不可思议的(dramatically)拥有了更多的处理器数量!

由于调度的基本单位是线程,一个线程上的一个program一次最多只能运行在一个处理器上。在一个双处理器(two-processor)系统上,一个单线程(single- threaded)program只能使用一半的CPU资源;在一个拥有100个处理器的系统中,百分之99的资源都是闲置的。另外,一个多线程的program可以同时的在多个处理器上执行。如果设计得当,多线程program可以通过更有效的利用可用处理器资源来达到改善吞吐量的目的。

在单处理器系统上使用多线程,也可以帮助我们提高吞吐量。如果一个program是单线程的,那么处理器就会在一个同步的I/O操作完成之前一直干等着。如果是多线程的program,当第一个线程正在等待I/O完成的时候,另外一个线程依然可以运行,这就使得应用程序在I/O阻塞的情况下,依然可以继续运行。(就像你在等待烧水的过程中读报纸一样,而不是一直等到水烧开了再开始读报纸。)

1.2.2. Simplicity of Modeling 简化建模

通常来说,你埋头只干一种类型的工作(比如:“修改这12个bug”)要比干几种类型的活要容易的多。(比如,你要修复bug,又要面试系统管理员的接任者,又要完成团队的绩效评估,还要做下周要演示的幻灯片,这么多事情当然很费劲啊)。

当你只有一种类型的工作要做的时候,你只需要专心的干,一直到工作完成就可以了;你不需要花心思来想下一步要做什么。另外一方面,如果要完成多种类型的任务,你就需要管理多个任务的优先级以及最后期限。而且还要从任务之间切换,这些切换通常都会带来额外的开销。费劲啊。

在软件方面也是一样的:一个只处理一种类型的串行任务的program是比较容易编写的。也没有什么致命的错误潜伏,而且要比一次管理多种任务的program更容易测试。如果为一个模型中的每种任务或每个元素(element)都分配一个线程,那么就会给你一种假象,什么假象呢?就是让你感觉任务是串行的,而且业务逻辑( domain logic)好像和调度的细节、交替执行的操作、异步的I/O以及资源等待等等都隔离开了。一个复杂的、异步的工作流可以被分解成很多个更简单的,同步的工作流,每个工作流都运行在单独的线程上,他们彼此只在某个特定的同步点的时候进行交互。

这些好处通常被一些框架所体现。比如servlets或RMI (Remote Method Invocation,远程方法调用)。框架负责处理请求的管理、线程的创建以及负载均衡以及在工作流合适的点上把收到的请求分发到对应的应用程序组件。Servlet的编写者不需要关注和担心在同一时间有多个其它请求正在被处理,也不需要担心socket的输入和输出的stream是否阻塞;当一个servlet的service方法被调用,然后响应web请求,它就可以同步的处理请求就好像是一个单线程的program一样。这就简化了组件的开发并且也缩短了使用这类框架的学习曲线。

1.2.3.Simplified Handling of Asynchronous Events 可以简化异步事件的处理

一个server 应用程序,这个server接受来自多个远程client的socket连接。

如果每个连接都分配有自己专有的线程并且允许使用同步I/O的话,那么开发起来就容易多了。

如果一个应用程序去从一个socket中读取数据的时候却没有数据的时候,那么这个读取一个阻塞直到有数据来了。在一个单线程的应用程序中,这就意味着你处理的请求将会停顿,而且在这个单线程阻塞的时候,其它所有的请求都将停顿。为了避免这样的问题,单线程的server 应用程序必须就要使用非阻塞(nonblocking)I/O,这个非阻塞的I/O是很复杂的而且有不少的坑(error prone),相比同步I/O来说。然而,如果我们的每个请求都有自己的线程的话,那么阻塞的问题就不会影响到其它请求的处理了。

以前的操作系统都限制了一个进程可以创建的线程的数量,几百个甚至更少。于是,操作系统就开发了一些有效的工具,针对多路I/O。你比如说Unix的select和poll系统调用,使用这些工具,你需要从java类库中获得有关非阻塞I/O的包(java.nio)的集合。然而,在现在的操作系统中,已经支持创建大数量的线程了,现在我们在某些平台上,甚至可以为每个client都分配一个线程。

1.2.4.More Responsive User Interfaces 用户界面响应更友好

图形用户界面(GUI)过去都是单线程的,所以在代码中你必须频繁的调用poll方法获得输入事件(这样做会让你的代码混乱不堪),或者你必须通过“主事件循环”(main event loop)间接得执行所有的应用程序代码。

如果从主事件循环中被调用的代码需要花很长时间来执行的话,那么用户界面上就会出现“冻结”(freeze)直到代码执行完成,因为只有控制权返回到主事件循环后后续的用户界面事件才能继续往下处理。

现在的图形用户界面(GUI)框架,比如AWT以及Swing工具包,都使用事件分发线程(EDT:event dispatch thread),代替过去的那种“主事件循环”的做法。当用户界面事件比如一个button按压发生后,那么应用程序定义好的事件处理器(handlers)就会在一个事件线程中被调用。由于大多数的GUI框架都是单线程子系统,所以主循环事件到现在为止实际上依然存在,但是现在它运行在自己的线程中,而且是由GUI的工具包来控制的,而不是由application来控制的。

如果在一个事件线程中只是执行一个短暂(short-lived)的任务,那么界面依然会保持灵敏的反应度,因为那个事件线程总是可以合理而快速的处理用户操作。然而,如果在一个事件线程中处理一个长运行(long-running)的任务,比如对一个很大的文档进行单词拼写检查或者从网络上获取一个资源。这种情况下,界面的响应度就大大降低了。如果用户在这个任务运行的时候来做一个操作,那么将会有很长的延迟,直到事件线程可以运行。更糟糕的是,不仅仅是UI变得不能响应了,卡掉了,而且我们也没可能取消这个长时间执行的任务,即使现在UI给你提供一个cancel的按钮。因为这个事件线程现在一直处于busy状态,所以不能处理这个cancel按钮的press事件,直到这个长任务执行完成后才能处理其它事件。

然而,如果这个长运行(long-running)的任务是由一个单独的线程来执行的话,那么事件线程就可以自由的去处理用户界面的事件,让UI的响应变得更加灵敏。

原文发布于微信公众号 - ImportSource(importsource)

原文发表时间:2016-06-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏不二小段

【爬虫军火库】下载保存图片(文件)

今天开始开一个新坑,暂且叫做【爬虫军火库】吧。以前一直想系统地写些东西,最终大都未能成文,想来我不适合发宏愿立长志,还是一步一个脚印地写点零碎的东西。有关爬虫,...

34280
来自专栏FreeBuf

一种几乎无法被检测到的Punycode钓鱼攻击,Chrome、Firefox和Opera等浏览器都中招

国内的安全专家最近发现一种新的钓鱼攻击,“几乎无法检测”,即便平时十分谨慎的用户也可能无法逃过欺骗。黑客可利用Chrome、Firefox和Opera浏览器中的...

25890
来自专栏美团技术团队

【美团技术团队博客】序列化和反序列化

摘要 序列化和反序列化几乎是工程师们每天都要面对的事情,但是要精确掌握这两个概念并不容易:一方面,它们往往作为框架的一部分出现而湮没在框架之中;另一方面,它们...

63190
来自专栏Linyb极客之路

框架设计原则

说说我的理解。这里其实是从框架结构的解读来解读,这里的包指的是 Maven 的 module。

10730
来自专栏mini188

openfire的组件(Component)开发

在之前的文章《Openfire阶段实践总结》中提到过一种openfire的扩展模式Compoent。本文将主要探讨对这种模式的应用与开发方法。 内部与外部组件介...

29180
来自专栏芋道源码1024

告诉你 Redis 是一个牛逼货

Redis 是一个 Key-Value 存储系统。和 Memcached 类似,它支持存储的 value 类型相对更多,包括 string(字符串)、 list...

15100
来自专栏纯洁的微笑

一次内存溢出的排查经历

OutOfMemoryError 问题相信很多朋友都遇到过,相对于常见的业务异常(数组越界、空指针等)来说这类问题是很难定位和解决的。

23820
来自专栏ChaMd5安全团队

NSCTF “表情包” 详细writeup

偶尔打了一下NSCTF,其中大家比较蒙蔽的有一题,叫“表情包”,是常见的颜文字。 ? 这种叫aaencode(可以把任意js编码成颜文字表情),然后在这里全选复...

484120
来自专栏Java架构

Java 10正式发布,最新特性全解读

19540
来自专栏大数据杂谈

Python 爬虫实战:股票数据定向爬虫

35540

扫码关注云+社区

领取腾讯云代金券