7.单线程并发

● 经典多线程架构

● 单线程/同线程架构

● 单线程架构的挑战

● 线程循环

    ○ 暂停线程循环

● 两种类型的任务

    ○ 重复任务

    ○ 一次性任务

● 单线程任务切换

    ○ 重复任务之间的任务切换

    ○ 一次性任务之间的任务切换

● 合并重复任务和一次性任务

● 任务平衡

    ○ 优先执行

    ○ 任务停泊

● 单线程并发的扩展


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

请注意:本教程仍在进行中。 在不久的将来会添加更多!

经典多线程架构

在经典的多线程体系架构中,通常将每个任务分配给一个单独的线程以执行。 每个线程一次只执行一个任务。 在某些设计中,将为每个任务创建一个新线程,因此一旦任务完成,该线程就会死掉。 在其他设计中,线程池保持活动状态,该线程池一次从任务队列中执行一个任务,然后执行另一任务,如此往复。有关更多信息,请参阅我的线程池教程。

多线程体系架构的优点是,相对容易地在多个线程和多个CPU之间分配工作负载。 只需将任务分配给线程,然后让OS / CPU将线程调度到CPU。

但是,如果正在执行的任务需要共享数据,则多线程体系架构可能会导致许多并发问题,例如竞态条件死锁饥饿滑动条件嵌套监视器锁定等。通常,越多的线程共享相同的数据和数据结构,发生并发问题的可能性就越高。 换句话说,您需要在设计时留意更多内容。

当多个线程试图同时访问同一个数据结构时,经典的多线程体系结构有时还会导致拥塞。 这取决于给定数据结构的实现方式,某些线程可能会被阻塞,以等待其他正在访问该数据结构线程访问完成。

单线程/同线程架构

经典多线程体系结构的替代方法是单线程或同线程。 通过仅使用一个线程来执行应用程序中的所有任务,就可以完全避免前一部分(经典的多线程并发架构)中列出的所有并发问题。

您可以扩展单线程体系结构以使用多个线程,其中每个线程的行为就像是一个单独的隔离的单线程系统。 在那种情况下,我将此架构称为相同线程。 执行任务所需的所有数据仍保持隔离在单个线程内-在同一线程内。

单线程架构的挑战

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

    ● 从任务中阻止IO操作,将阻止线程,从而阻止整个应用程序。

    ● 长时间运行的任务,可能会产生无法接受的延迟其他任务的执行。

    ● 单个线程只能使用到单个CPU。

可以解决这些问题,但又不会失去单线程并发体系结构的简单性的优势,也不会使整体设计过于复杂。

线程循环

大多数长时间运行的应用程序以某种循环执行,其中应用程序主线程正在等待来自应用程序外部的输入,处理该输入,然后返回等待状态。

线程循环

这种线程循环在服务器应用程序(Web服务,服务等)和GUI应用程序中都可以使用。 有时,您可以看到该线程循环, 而有时则看不到。

暂停线程循环

您可能会想知道,在一遍又一遍地密集循环中,执行的线程是否会浪费大量CPU时间。 如果线程在运行时没有任何实际工作要做,那么可能会浪费掉大量CPU时间。 因此,如果执行循环的线程判断出休眠几毫秒是可行的,则可能会使用“休眠”,而非循环, 这样可以减少CPU的时间浪费。

两种类型的任务

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

    ● 重复任务

    ● 一次性任务

以下各节将对这两项任务进行更详细的说明。

重复任务

重复任务是一个重复执行的任务,它在执行该任务的线程的生命周期内一次又一次地执行。 通常,对于任务的每次调用,将完全执行重复的任务。

重复任务的一个示例是检查一组入站网络连接上的传入数据。 如果检测到任何传入数据,将对其进行处理,并且在处理之后,将针对此特定调用执行重复的任务。 但是,需要一次又一次地检查入站数据,以使应用程序能够连续响应传入的数据。

重复任务

一次性任务

一次性任务是只需要执行一次的任务。一次性任务可以是短期运行,也可以是长期运行。

短时任务是一个足够短的任务,可以在一个执行阶段中完成,而又不会使执行该任务的线程因该线程承担的其他职责(它必须执行的其他任务)而延迟。

一次性长时间运行的任务是在单个执行阶段中花费太长时间才能完成的任务。 “花费太长时间”是指执行任务中的全部工作量将占用太多的线程时间,因此其他重复任务或一次性任务将被延迟太多,以至于应用程序的总响应速度受到伤害。

为了避免单个长时间运行的任务占用过多的线程执行时间,将完成任务所需的全部工作分解为较小的块,可以一次执行一个块。每个块必须足够小,以免延迟线程执行过多任务所需的其他任务。

长时间运行的任务在内部跟踪其执行块。执行长时间运行的任务的线程将多次调用其执行方法,直到所有任务块均已完全执行。在调用特定长时间运行任务的执行方法之间,线程可以调用其他长时间运行任务,其他重复任务或线程承担的任何职责的执行方法。

一次性的长期运行任务可能是处理目录中的N个文件。可以将N个文件的处理分解成较小的块,而不是在单个执行阶段中处理所有N个文件,而每个块都在单个执行阶段中进行处理。例如,每个执行阶段可以处理1个文件。要处理所有N个文件的任务

在线程循环中,一次性任务通常由重复任务检测并执行,如下所示。

一次性任务

单线程任务切换

为了能够似乎同时在一个以上的任务上取得进展,在任务上取得进展的线程必须能够在这些任务之间进行切换。 这也称为任务切换。

任务切换的确切工作方式取决于任务的类型-线程是在重复任务还是一次性任务之间进行切换。 虽然总的原理还是一样的。 我将在以下各节中对这两者进行更详细的说明。

重复任务之间的任务切换

重复的任务通常只有一个方法,该方法被同一线程重复调用。 重复任务是应在应用程序的整个生命周期中重复的任务,因此它永远不会真正“完成”。 重复的任务执行了所需的操作,然后退出其执行方法,将控制权交还给调用线程。

通过以循环方式调用它们的执行方法,单个线程可以在多个重复任务之间进行切换。 首先重复执行的任务A有执行的机会,然后是B,然后是C,然后是A,依此类推。

万一重复任务没有完全完成它开始的任何工作,它可以记录它在内部走了多远,并在下次调用重复任务时从那里继续。

重复任务之间的任务切换

一次性任务之间的任务切换

一次性任务与重复任务的不同之处在于,一次性任务有望在某个时间点完成。 这意味着,有时需要从任务池中删除一次性任务。

除此之外,一次完成任务之间的切换类似于重复任务之间的切换。 执行线程调用给定的一次性任务的执行方法,该任务在短时间内取得进展,然后在内部记录其执行的距离,然后退出其执行方法,将控制权交还给调用线程。 现在,调用线程可以循环方式调用任务池中的下一个一次性任务。

每次调用一次性任务的执行方法后,调用线程将检查任务是否已完成。 如果已删除,则一次性任务将从任务池中删除。

一次性任务之间的任务切换

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

在实践中,一个应用程序可能包含一个调用一个或多个重复任务的线程循环,重复任务可以执行一次任务作为重复行为的一部分。 下图说明了这一点。 该图仅描述了一个重复的任务,但根据具体应用,可能还会有更多任务。

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

任务平衡

当单个线程要在多个任务(无论是重复任务还是一次性任务)之间切换时,必须确保在一次调用任务时,这些任务不会占用过多的线程执行时间。换句话说,确保每个任务之间执行时间的公平平衡是每个任务帮助的职责。

任务应该允许自己执行多长时间,具体取决于系统设计者。对于一次性任务,这可能会有些复杂。有些任务自然很快就完成了,而另一些任务自然要花费更长的时间才能完成。对于运行时间较长的任务,由任务的实现者来估计如何将工作分解为足够小的分区,以便可以在不延迟其他任务过多的情况下执行每个分区。

需要注意的一件有趣的事是,如果线程以循环方式调用每个一次性任务,那么任务执行器包含的一次性任务越多,每个线程获得的执行时间就越短,因为在执行任务之前需要更长的时间接下来的执行时间。

优先执行

可以实现一个将某些任务优先于其他任务的任务执行器。 例如,任务执行者可以在内部将任务保存在不同的列表中,例如 执行低优先级任务列表中的任务每执行1次,就执行2次高优先级列表中的任务。

确切地说,如何执行优先任务执行器将取决于具体需求。 还有多少个优先级,例如 低/高,或低/中/高等

任务停泊

如果一次性任务正在等待某些异步操作完成,例如 如果来自远程服务器的答复,则一次性任务将无法继续进行下去,直到它正在等待的异步操作完成为止。 在那种情况下,一次又一次地调用该任务可能没有意义,只是为了使该任务意识到它无法取得任何进展并立即将控制权返回给调用线程。

在这种情况下,一次性任务能够将自己“停放”在任务执行器内部可能是有意义的,因此不再被调用。 异步操作完成后,一次性任务可以取消停放,然后重新插入到活动任务中,这些活动将连续调用以取得进展。 当然,要能够取消任务,系统的其他部分必须检测到异步操作已完成,以及要为该异步操作取消任务。

单线程并发的扩展

显然,如果在应用程序中只有一个线程正在执行,则不能利用多个CPU。 解决方案是启动多个线程。 通常,每个CPU一个线程-取决于您的线程需要执行哪种任务。 如果您有需要执行阻塞IO工作的任务,例如从文件系统或网络中读取数据,则每个CPU可能需要多个线程。 每个线程将在等待阻塞的IO操作完成时被阻塞,不执行任何操作。

单线程并发的扩展

当您将单线程体系结构扩展到多个单线程子系统时,从技术上讲,它不再是单线程的。 但是,每个单线程子系统通常都将被设计为一个单线程系统,并表现为一个单线程系统。 我曾经将这样的多线程单线程系统称为同线程系统,尽管我不确定这实际上是最精确的术语。 我们可能需要重新审视这些不同的设计,并在将来为它们提供更具描述性的术语。


译自:Singlethreaded Concurrency

Jakob Jenkov

Last update: 2020-12-11

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容