单线程并发

本文探讨了单线程并发的概念,它避免了多线程中的并发问题,但也带来了如IO阻塞和任务调度的挑战。文章介绍了线程循环、重复任务与一次性任务的执行,以及如何在它们之间进行任务切换。此外,还讨论了任务均衡、优先执行和任务等待策略。最后,提到了如何扩展单线程架构以利用多核CPU。
摘要由CSDN通过智能技术生成

本文翻译自http://tutorials.jenkov.com/java-concurrency/singlethreaded-concurrency.html,机翻加人工校正,仅供学习交流。

单线程并发

单线程并发意味着从一个线程内似乎同时在多个任务上取得进展。从表面上看,单线程并发听起来有点矛盾。在以前多线程体系结构中,多个任务将被分配到多个线程中,以便并发执行。因此,不同任务之间的切换是通过操作系统和CPU之间的不同切换来完成的。实际上,一个线程可以同时在多个任务上取得进展。在这个单线程并发性教程中,我将解释如何实现的以及单线程并发设计的好处。
请注意:本教程仍在进行中。更多将添加在不久的将来!

典型的多线程架构

在一个经典的多线程架构中,通常将每个任务分配给单独的线程执行。每个线程一次只执行一个任务。在某些设计中,将为每个任务创建一个新线程,因此,一旦任务完成,线程就会死亡。在其他设计中,线程池保持活动状态,每次从任务队列中获取一个任务,然后执行它,然后再执行另一个任务。有关更多信息,请参阅我的线程池教程。
多线程架构有着在多个线程和多个CPU之间分配工作负载相对容易的优势,只需将任务交给一个线程,然后让操作系统/ CPU将该线程调度到一个CPU。
但是,如果正在执行的任务需要共享数据,多线程体系结构可能导致许多并发问题,例如竞争条件、死锁、饥饿、滑动条件、嵌套监视器锁定等。一般而言,越多的线程共享相同的数据和数据结构,并发问题发生的可能性越大。换句话说,你更需要仔细检查你的设计。
当多个线程试图同时访问相同的数据结构时,一个经典的多线程体系结构有时也可以会导致拥塞。根据给定的数据结构的实现情况,当其他线程正在访问数据结构时,有些线程可能阻塞等待访问。

单线程/同一线程架构

经典多线程体系结构的替代方案是单线程或同一线程。通过只让一个线程执行应用程序中的所有任务,您完全避免了前一节中列出的所有并发问题。
您可以将单线程架构扩展为使用多个线程,,每个线程的行为就像一个单独的、孤立的单线程的系统。在这种情况下,我将架构称为同一线程。执行任务所需的所有数据仍然在单个线程中保持隔离——在同一个线程中。

单线程架构挑战

如果只有一个线程执行应用程序的所有任务,这可能会导致一些问题:

  • 在任务中阻塞IO操作将阻塞线程,从而阻塞整个应用程序。
  • 长时间运行的任务可能会不可接受地延迟其他任务的执行。
  • 单个线程只能使用单个CPU。
    在不失单线程并发体系结构的简单性优势,又不会让整个设计过于复杂同时,这些问题都是可以解决的。

线程循环

大多数长时间运行的应用程序以某种形式的循环执行,主应用程序线程正在等待来自应用程序外部的输入,处理输入,然后返回等待。
在这里插入图片描述
这种线程循环既用于服务器应用程序(web服务,服务等),也用于GUI应用程序。有时这个线程循环对您是隐藏的,有时不是。

暂停线程循环

您可能想知道一个线程在一个紧密循环中反复执行是否会浪费大量CPU时间,如果线程运行时没有任何实际工作要做,那么是的,这可能会浪费大量CPU时间。但是,如果执行循环的线程估计睡眠几毫秒是没问题的,它可以自由地“休眠”,这样可以减少CPU时间浪费。

两种类型的任务

一个线程循环在其生命周期内通常执行两种类型的任务:

  • 重复任务
  • 一次性的任务
    这两个任务将在下面的部分中进行更详细的解释。
重复任务

重复的任务是一个周期性的任务,在执行它的线程的生命周期内一次又一次地执行。通常,一个重复任务在每次调用时都被完全执行。重复任务的一个例子是检查一组入站网络连接上的输入数据。如果检测到任何传入数据,它将被处理,在处理完这个特定的调用之后,重复的任务就完成了。对于应用程序,需要一次又一次地重复检查入站数据去连续响应输入的数据。
在这里插入图片描述

一次性任务

一次性任务是指只需要执行一次的任务。一次性任务可以是短期运行的,也可以是长期运行的。
短时间运行任务是指短到可以在单个执行阶段完成的任务,不用延迟线程执行其他的任务(必须执行的其他任务)。一次性长时间运行的任务是指在单个执行阶段中耗时太长而无法完成的任务。我说的"耗时太长"是指执行任务中的总工作量会占用太多的线程时间,所以其他重复的任务或者一次性的任务会被延迟,以至于应用程序的总响应性受到损害。
为了避免单个长时间运行的任务占用线程太多的执行时间,完成任务所需的全部工作被分解成更小的可以一次执行块。每个执行块必须足够小,不太多延迟线程需要执行的其他任务。
长时间运行的任务在内部跟踪它们的执行块。执行长时间运行的任务的线程会多次调用它的执行方法,直到全部任务块都已完全执行。在调用特定长时间运行任务的执行方法之间,线程可以调用其他长时间运行的任务的执行方法,或者其他重复的任务,或者线程的任何任务。
一个一次性的长时间运行的任务可能在一个目录中处理N个文件。N个文件的处理可以被分解成更小的块,每一个都在一个执行阶段中处理,而不是在单个执行阶段处理所有N个文件。例如,每个执行阶段可以处理一个文件。在一个线程循环中处理所有的N个文件,一次性任务通常由重复任务检测和执行,图所示如下
在这里插入图片描述

单线程任务切换

为了能够同时在多个任务上取得进展,在任务上取得进展的线程必须能够在任务之间切换,这也被称为任务切换。
无论线程是否在重复任务或一次性任务之间切换,任务切换的工作方式取决于任务的类型。总的原则是一样的。我将在下面的部分中更详细地解释这两种情况。

重复任务间的任务切换

重复的任务通常有一个由同一个线程重复调用的方法。重复任务是指应该在应用程序的整个生命周期内重复的任务,所以它永远不会真正“完整”。重复的任务完成了它需要做的事情,然后退出执行方法,将控制权交还给调用线程。单个线程以循环式的方式,可以通过调用它们的执行方法在多个重复任务之间切换。首先重复的任务A有机会执行,然后是B,然后是C,然后是A,等等。
如果一个重复的任务没有完全完成它开始的任何工作,它可以记录内部到达的距离,然后在下次重复的任务被调用时继续。
在这里插入图片描述

一次性任务间的任务切换

一次性任务与重复任务的区别在于一次性任务在某一时刻会完成的,这意味着,有时需要从任务池中删除一次性任务。
除此之外,在一次性任务之间切换类似于在重复任务之间切换。执行线程调用给定的一次性任务的执行方法,任务在短时间内取得进展,然后在内部记录它走了多远,然后退出它的执行方法,将控制权交还给调用线程。调用线程现在可以以循环的方式调用任务池中的下一个一次性任务。
每次调用一次性任务的执行方法后,调用线程将检查任务是否已经完成。如果完成,则将一次性任务从任务池中移除。
在这里插入图片描述

结合重复任务和一次性任务

在实践中,应用程序可能由一个线程循环组成,该线程循环调用一个或多个重复任务,重复任务可能会执行一次性任务,作为其重复行为的一部分。下面的图表说明了这一点。该图只描述了一个重复的任务,但还可能有更多,这取决于具体的应用。在这里插入图片描述

任务均衡

无论是重复任务还是一次性任务,当一个线程要在多个任务之间切换时,在对任务的单个调用期间,这些任务不会占用线程的太多的执行时间是很重要的。换句话说,每个任务的职责都是确保任务之间执行时间的公平分配。
一个任务应该允许自己执行多长时间是由系统设计者决定的。对于一次性任务来说,这可能有点复杂。有些任务自然很快就能完成,而有些任务自然需要更长的时间才能完成。对于长时间运行的任务,取决于任务的实现者需要评估如何将工作分解成足够小的部分,这样每个分区都可以在不过多延迟其他任务的情况下执行。
值得注意的是,如果线程以轮询的方式调用每个一次性任务,任务执行器包含的一次性任务越多,每个线程的执行时间越少,因为在任务下一次得到执行时间之前需要更长的时间。

优先执行

可以实现某些任务优先于其他任务的任务执行器。例如,任务执行器可以在内部保持不同列表中的任务,每执行1次低优先级任务列表中的任务时,就执行优先级列表中的任务2次。具体如何实现优先级任务执行器将取决于具体需求,还有优先级层次,例如低/高,或低/中/高等。

任务等待

如果一个一次性任务正在等待某个异步操作完成,例如一个来自远程服务器的回复,在它等待的异步操作完成之前,一次性任务将无法取得任何进展。只是为了让任务意识到它不能取得任何进展,并立即将控制权返回给调用线程,在这种情况下,反复调用这个任务是没有意义的。
在这种情况下,一次性任务能够任务等待在任务执行程序中是有意义的,它不再被调用。当异步操作完成时,一次性任务可以被取消并重新插入被不断调用以取得进展的活动任务中。当然,为了实现任务等待——系统的其他部分必须检测到异步操作已经完成并且为异步操作解除等待的任务。

扩展单线程的并发性

显然,如果应用程序中只执行一个线程,就不能利用多个CPU,解决方案是启动多个线程。通常,每个CPU一个线程——这取决于您的线程需要执行的任务类型。如果有任务需要执行阻塞的IO工作,比如从文件系统或网络读取,那么每个CPU可能需要多个线程。每个线程在等待阻塞的IO操作完成时将被阻塞,什么也不做。
在这里插入图片描述
您将单线程架构扩展为多个单线程子系统时,它在技术上不再是单线程的。然而,每个单线程子系统通常都被设计为单线程系统,并表现为单线程系统。我曾经把这种多线程单线程系统称为同一线程系统,尽管我不确定这是否是最精确的术语。我们可能需要重新审视这些不同的设计,并在将来为它们想出更具描述性的术语。

下一章:创建并启动一个Java线程(尚未翻译)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值