1.并发的发展历史

其实,在早期计算机并没有包含操作系统,这个时候,这个计算机只跑一个程序,这个程序独享计算机的所有资源,这个时候不存在什么并发问题,但是对计算机的资源来说,确实是一种浪费。

于是,操作系统出现了,操作系统改变了这种现状,让计算机可以运行多个程序,并且不同的程序占用独立的计算机资源,如内存,CPU等。

操作系统的出现,主要有以下几点原因:

1,资源利用率:可以在其他程序执行过程出现等待的时候,去执行其他程序,从而提高程序的利用率。什么时候会出现等待,比如输入操作或输出操作。

2,公平性:所有的程序可以共享计算机资源,一种有效的方式是通过时间片的方式来让程序共享计算机资源。

3,在编写多任务程序时,可以一个程序执行一个任务,必要时,程序之间进行通信即可。

当计算机从单程序变成多程序之后,这个时候又发展出了多线程,线程是进程里面的每个执行控制流,或叫执行路线。如果没有明确的协同机制,那么每个线程将独立运行,共享着进程的内存及CPU资源等等。

2.并发的优势

即使编写多线程程序具有挑战性,但它仍在使用中,是因为它可以带来如下的好处:

  1. 更好的资源利用

  2. 在某些场景下程序的设计会更简单

  3. 提升程序的响应性

2.1 更好的资源利用

假设一个应用会从本地文件系统中读取和处理文件。我们假设从磁盘上读取一个文件花费5秒钟并且处理它会花费2秒钟。那么处理两个文件会花费:

  5 seconds reading file A
  2 seconds processing file A
  5 seconds reading file B
  2 seconds processing file B
-----------------------
 14 seconds total

从磁盘上读取文件的时候,大多数的CPU时间都会花费在等待磁盘来读取数据。在这个时候CPU是相当空闲的。在这个时候它可以干点别的事情。通过改变操作的顺序,CPU可以得到更好的利用。看一下下面的这种操作顺序:

  5 seconds reading file A
  5 seconds reading file B + 2 seconds processing file A
  2 seconds processing file B
-----------------------
 12 seconds total

CPU会等待第一个文件的读取。然后他开始读取第二个文件。当第二个文件正在被读取的时候,CPU可以处理第一个文件。注意,当等待文件从磁盘上读取的时候,CPU大多数是空闲的。

一般来说,CPU可以在等待IO操作的时候做一些其他的事情。当然不一定只是磁盘IO会阻塞,网络IO也会阻塞,或者等待来自机器中一个用户的输入。网络和磁盘的IO经常比CPU和内存的IO慢得多。

2.2 更简单的程序设计

如果你准备编写通过在单线程应用中手工操作文件的读取和操作顺序的程序,你就必须要跟踪每个文件读取和处理的状态。相反,你可以启动两个线程,每个线程仅仅只读取或者处理单个文件。这两个线程都会因为等待磁盘读取文件而阻塞。在等待的时候,其他的线程可以使用CPU来处理它们已经读取的那部分文件。结果就是,磁盘一直会处于忙碌状态,把不同的文件读到内存中。这样可以带来更好的磁盘和CPU的利用。这样的程序更容易编写,因为每个线程仅仅需要跟踪单个文件。

2.3 提升程序的响应性

将单线程应用转为多线程应用另一个主要目的是为了使应用响应更好。假设一个服务器应用会在某个端口监听即将到来的请求。当接收到请求的时候,它会处理请求然后继续回去监听。服务器的循环可以抽成这样的:

while(server is active){
    listen for request
    process request
}

如果要花很长的时间去处理请求的话,那么在这期间就没有新的客户端可以发送请求到服务器了。只有服务器在监听的时候才可以接收请求。

另一种设计是把监听线程的请求传给后台的工作线程,然后立即转回去监听。工作线程将会处理请求并给客户端发送响应。这种设计是:

while(server is active){
    listen for request
    hand request to worker thread
}

这种情况下,服务器线程会更快地监听请求。就有更多地客户端可以向服务器发送请求。服务器因此而更具有响应性。

对于桌面应用也是这样的。如果你点击一个按钮开启一个耗时很长的任务,并且执行任务的线程同时也是更新窗口,按钮等的线程,那么应用会在处理任务的时候表现得响应性很差。其实可以把任务交给后台的线程。当后台线程繁忙地处理任务地时候,窗口线程可以继续响应用户其他地请求。当工作线程把任务处理完毕之后,可以给窗口线程发送信号。这时,窗口线程可以根据任务处理的结果来更新应用窗口。采用后台线程程序可以对用户表现得更具有更好得响应。

3.并发的弊端

多线程的缺点:

1.如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换.

2.更多的线程需要更多的内存空间

3.线程中止需要考虑对程序运行的影响.

4.通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生

3.1 关于弊端

所有的弊端,反过来思考就是我们需要优化和提升的地方。

如何避免死锁?如何保证线程安全?如何减少线程之间的切换?

4. 应用场景

4.1 什么时候使用多线程  

(1) 耗时或大量占用处理器的任务阻塞用户界面操作;

多线程程序一般被用来在后台执行耗时的任务。主线程保持运行,并且工作线程做它的后台工作。对于Windows Forms程序来说,如果主线程试图执行冗长的操作,键盘和鼠标的操作会变的迟钝,程序也会失去响应。由于这个原因,应该在工作线程中运行一个耗时任务时添加一个工作线程,即使在主线程上有一个有好的提示“处理中…”,以防止工作无法继续。这就避免了程序出现由操作系统提示的“没有相应”,来诱使用户强制结束程序的进程而导致错误。模式对话框还允许实现“取消”功能,允许继续接收事件,而实际的任务已被工作线程完成。BackgroundWorker恰好可以辅助完成这一功能。

(2) 各个任务必须等待外部资源 (如远程文件或 Internet连接)。

在没有用户界面的程序里,比如说Windows Service, 多线程在当一个任务有潜在的耗时,因为它在等待另台电脑的响应(比如一个应用服务器,数据库服务器,或者一个客户端)的实现特别有意义。用工作线程完成任务意味着主线程可以立即做其它的事情。

ps: 我想当了 ajax 这种异步代替同步,提升客户体验。

4.2 什么时候不使用多线程  

同样的 ,多线程也存在许多缺点 ,在考虑多线程时需要进行充分的考虑。多线程的主要缺点包括:

(1)等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源 ,如打印机等。

(2)对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。当这种负担超过一定程度时,多线程的特点主要表现在其缺点上,比如用独立的线程来更新数组内每个元素。

(3)线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。

(4)对公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,从而使前一个线程的参数被修改;另外 ,当公用变量的读写操作是非原子性时,在不同的机器上,中断时间的不确定性,会导致数据在一个线程内的操作产生错误,从而产生莫名其妙的错误,而这种错误是程序员无法预知的。

参考资料

  • 发展历史

http://blog.51cto.com/huangguizhao/2059874

  • 好处 & 缺点

http://tutorials.jenkov.com/java-concurrency/benefits.html

https://www.cnblogs.com/tuhooo/p/9075077.html

https://www.cnblogs.com/gylhaut/p/5264088.html

https://www.cnblogs.com/qtiger/p/5812483.html