并发性:如何处理设计和调试实现?


37

我已经开发并发系统已有好几年了,尽管我缺乏正规的培训(即没有学位),但我对这个主题掌握得很好。最近至少有一些新的语言变得流行起来,这些语言至少是为了简化并发而设计的,例如Erlang和Go。似乎他们的并发方法呼应了我自己的经验,即如何使系统具有可伸缩性并利用多个内核/处理器/机器。

但是,我发现很少有工具可以帮助您可视化您打算做什么,并可以验证您至少与最初的设想接近。使用非专为并发设计的语言(例如C / C ++,C#,Java等),调试并发代码可能是一场噩梦。特别是,几乎不可能在开发环境中的一个系统上重新创建容易发生的条件。

那么,您设计系统来处理并发和并行处理的方法是什么?例子:

  • 您如何确定可以同时进行的和必须进行顺序的?
  • 您如何重现错误条件并查看应用程序执行时发生的情况?
  • 您如何可视化应用程序的不同并发部分之间的交互?

对于这些问题,我有自己的答案,但我还想了解更多。

编辑

到目前为止,我们有很多不错的建议。链接到的许多文章都非常好,我已经阅读了其中的一些文章。

我在并发编程方面的个人经历使我相信与顺序编程相比,您需要不同的思维方式。精神鸿沟可能与面向对象编程和过程编程之间的差异一样大。我希望这组问题更多地集中在系统地解决问题所必需的思维过程(即理论)上。提供更具体的答案时,有助于举例说明-您亲自进行了一些研究。

赏金目标

不要告诉我该怎么办。我已经控制住了。告诉我你做什么。告诉我如何解决这些问题。


这是一个好问题-很多可能的深度。我还对Java中的多线程应用程序有一些很好的经验,但是想了解更多。
Michael K 2010年

到目前为止,我们有一些不错的答案。任何人都想冒险尝试一下您在该领域需要帮助的地方吗?
Berin Loritsch 2010年

用于并发编码的TotalView Debugger是一个非常有用的工具,尽管需要一些学习曲线-totalviewtech.com/products/totalview.html
Fanatic23,2010年

也许日志记录可以帮助您解决最后两个问题。
阿米尔·雷扎伊

我正在寻找的是人们的流程。这些是我一直使用的工具不足的地方,但是可以完成工作。我不太关心引用别人的文章,而更关心这里的方法论。
Berin Loritsch 2010年

Answers:


11

我已经开发并发系统已有好几年了,尽管我缺乏正规的培训(即没有学位),但我对这个主题掌握得很好。

我认识的许多最好的程序员都没有读完大学。至于我,我学习哲学。

C / C ++,C#,Java等)。特别是,几乎不可能在开发环境中的一个系统上重新创建容易发生的条件。

您如何确定可以同时进行的和必须进行顺序的?

我们通常从一个1000英里高的隐喻开始,以向我们自己(第一)和他人(第二)阐明我们的体系结构。

当我们面对这个问题时,我们总是找到一种方法来将并发对象的可见性限制为非并发对象。

最近,我在scala中发现了Actors,我看到我的旧解决方案是一种“ miniactor”,其功能远不如scala的微型。所以我的建议是从那里开始。

另一个建议是跳过尽可能多的问题:例如,我们使用集中式缓存(兵马俑)而不是将映射保留在内存中,使用内部类回调而不是同步方法,发送消息而不是编写共享内存等。

无论如何,使用scala都更加容易。

您如何重现错误条件并查看应用程序执行时发生的情况?

这里没有真正的答案。我们有一些用于并发的单元测试,并且我们有一个负载测试套件,它会尽可能地给应用程序施加压力。

您如何可视化应用程序的不同并发部分之间的交互?

再次没有真正的答案:我们在白板上设计“隐喻”,并尝试确保体系结构方面没有冲突。

对于Arch,我的意思是尼尔·福特(Neal Ford)的定义:Sw Architecture是以后将很难更改的所有内容。

编程使我相信,您需要与顺序编程不同的思维方式。

也许对我而言,根本不可能以并行的方式进行思考,因此,以不需要并行思考的方式更好地设计我们的软件,并使用清晰的防护栏避免并发通道之间的崩溃。


6

对我来说,所有的都是数据。正确破坏数据,并行处理很容易。保留,死锁等所有问题都将消失。

我确实知道这不是并行化的唯一方法,但对我而言,这是最有用的。

为了说明一个(不是那么快)的故事:

从2007年到2009年,我确实在大型财务(股票市场控制)系统上工作,数据的处理量非常大。为了说明这一点,在一个普通客户的工作站上,对一个客户的一个帐户进行的所有计算大约花费1-3秒,并且帐户总数超过3万。每天晚上,关闭系统都会给用户带来很大的痛苦(通常需要6个小时以上的处理时间,而不会给用户带来任何差错)。

对问题的进一步研究表明,我们可以在多台计算机之间并行计算,但是在旧的数据库服务器(模拟SQL 6.5的SQL 2000服务器)上,我们仍然会遇到巨大的瓶颈。

很显然,我们的最小处理包是单个帐户的计算,而最大的瓶颈是数据库服务器的保留(我们可以在“ sp_who”上看到几个等待执行相同处理的连接)。因此并行过程如下:

1)一个单一的生产者,负责按顺序读取或写入数据库。此处不允许并发。生产者为消费者准备了一系列的工作。该数据库完全属于该生产者。

2)几台计算机上的多个消费者。每个使用者都从队列接收了整包数据,准备进行计算。每个请求操作都是同步的。

3)计算之后,每个使用者将数据发送回内存同步队列中的生产者,以保留数据。

有多个检查点,有几种机制可以确保正确保存交易(不遗漏任何交易),但是整个工作值得。最后,在10台计算机(加上生产者/队列计算机)中进行的计算使整个系统的关闭时间减少到15分钟。

仅仅消除了糟糕的并发管理SQL 6.5带来的保留问题,就给了我们很大的优势。其余的几乎是线性的,添加到“网格”的每台新计算机都减少了处理时间,直到我们达到了对数据库进行顺序读/写操作的“最大效率”。


2

在多线程环境中工作很艰巨,需要编码纪律。您需要遵循正确的准则来获取锁,释放锁,访问全局变量等。

让我一一回答你的问题

* How do you figure out what can be made concurrent vs. what has to be sequential?

使用并发

1)轮询:-需要一个线程来连续轮询某些事物或定期发送更新。(诸如“心比特”之类的概念会定期将一些数据发送到中央服务器,以表示我还活着。)

2)I / O繁重的操作可以并行进行。最好的例子是记录器。记录器线程可以是单独的线程。

3)针对不同数据的相似任务。如果有一些任务发生在不同的数据上,但本质上非常相似,则不同的线程可以执行此任务。最好的例子是服务器请求。

当然,根据应用程序,许多其他类似的对象也是如此。

* How do you reproduce error conditions and view what is happening as the application executes?

在日志中使用日志和调试打印。尝试同时记录线程ID,以便您查看每个线程中发生的情况。
产生错误情况的一种方法是将故意的延迟(在调试代码中)放置在您认为问题发生的位置,并强行停止该线程。类似的事情也可以在调试器中完成,但到目前为止我还没有完成。

* How do you visualize the interactions between the different concurrent parts of the application?

将日志放入锁中,以便您知道谁在何时何地锁定以及谁在尝试锁定。正如我之前所说,尝试将线程ID放入日志中以了解每个线程中发生了什么。

这只是我对多线程应用程序工作大约3年的建议,希望对您有所帮助。


2
  • 您如何确定可以同时进行的和必须进行顺序的?

我首先要问的是,应用程序(或组件)是否会真正从并发处理中受益,或者从外行的角度来说,瓶颈在哪里?并发显然不会总为使它起作用所需的投资提供好处。如果它看起来像候选人,那么我将自下而上地工作-试图找到可以孤立地有效完成其工作的最大操作或一组操作-我不想为微不足道,成本低廉的操作增加线程运营-我在寻找演员

与Erlang一起工作,我绝对喜欢使用异步消息传递的概念以及用于并发的actor模型-它直观,有效且简洁。

关的理解演员并发

参与者模型包含一些关键原则:

  • 没有共享状态
  • 轻量级工艺
  • 异步消息传递
  • 用于缓冲传入消息的邮箱
  • 具有模式匹配的邮箱处理

演员是执行功能的过程。这里的进程是一个轻量级的用户空间线程(不要与典型的重量级操作系统进程相混淆)。参与者永远不会共享状态,因此永远不需要竞争锁来访问共享数据。相反,参与者通过发送不可变的消息来共享数据。不变的数据无法修改,因此读取不需要锁定。

与锁定和共享数据相比,Erlang并发模型更易于理解和调试。隔离逻辑的方式很容易通过将消息传递给组件来进行组件测试。

与并发系统一起使用,这几乎就是我的设计以任何语言工作的方式-多个线程将从中提取数据,执行简单操作并重复或推回队列的队列。Erlang只是强制执行不可变的数据结构,以防止产生副作用,并降低创建新线程的成本和复杂性。

该模型不是Erlang独有的,即使在Java和.NET世界中,也存在创建该模型的方法-我将研究并发和协调运行时(CCR)Relang(还有Java的Jetlang)。

  • 您如何重现错误条件并查看应用程序执行时发生的情况?

根据我的经验,我唯一能做的就是承诺跟踪/记录所有内容。每个进程/线程都需要具有一个标识符,每个新的工作单元都需要具有一个关联ID。您需要能够查看您的日志并准确跟踪正在处理的内容以及何时进行处理,我发现没有什么魔术可以消除这种情况。

  • 您如何可视化应用程序的不同并发部分之间的交互?

见上文,这很丑陋,但有效。我唯一要做的另一件事是使用UML序列图-当然,这是在设计时进行的-但您可以使用它们来验证您的组件是否按照您想要的方式说话。


1

-我的答案是针对MS / Visual Studio的-

您如何确定可以同时进行的和必须进行顺序的?

这将需要领域知识,这里不会有任何笼统的声明来涵盖它。

您如何重现错误条件并查看应用程序执行时发生的情况?

大量日志记录,能够在生产应用程序中打开/关闭/启动日志记录,以便在生产中捕获它。 VS2010 Intellitrace应该可以解决这个问题,但是我还没有使用过它。

您如何可视化应用程序的不同并发部分之间的交互?

我对此没有很好的答案,很想看到一个。


日志记录将更改代码的执行方式,从而可能会导致您在不显示代码后出现错误。
马修(Matthew)

1

我不同意您的说法,即C不是为并发而设计的。C是为通用系统编程而设计的,并且坚韧不拔地指出了要做出的关键决策,并将在未来几年内继续这样做。即使最好的决定可能是不使用C,也是如此。此外,C中的并发难度仅与设计复杂性一样高。

我尽我所能尝试实现锁,最终实现真正实用的无锁编程对我来说可能成为现实。所谓锁定,并不是说互斥,而是指无需仲裁即可实现安全并发​​的过程。实际上,我的意思是说移植起来比实现起来容易。我也很少接受正式的CS培训,但是我想我可以允许:)

在那之后,我遇到的大多数错误都变得相对浅薄,或者完全被我所困扰,以至于我退回到了一家酒吧。仅当对程序进行概要分析时,pub才成为有吸引力的选择,它会充分降低它的速度以暴露与我尝试查找的内容无关的其他种族。

正如其他人指出的那样,您描述的问题是特定于域的。我会尽我所能避免可能在任何情况下都需要仲裁的案件。如果这看起来像是富裕的痛苦,那么我将重新评估为多个线程或进程提供并发和非序列化访问某项的选项。

然后,再次在其中“分配”,仲裁成为必须。你有一个具体的例子吗?


为了澄清我的陈述,C不是专门为并发设计的。这与Go,Erlang和Scala之类的语言在设计时明确考虑了并发性形成了鲜明的对比。我没打算说你不能做的并发性C.
帐户berin Loritsch

1

您如何重现错误条件并查看应用程序执行时发生的情况?

您如何可视化应用程序的不同并发部分之间的交互?

根据我的经验,对这两个方面的回答如下:

分布式跟踪

分布式跟踪是一种技术,可捕获系统中每个并行并发组件的时序数据,并以图形格式显示给您。并发执行的表示始终是交错的,使您可以查看并行运行的内容和并行运行的内容。

分布式跟踪的起源是(当然)分布式系统,根据定义,该系统是异步的并且高度并发。具有分布式跟踪的分布式系统使人们能够:

a)确定重要的瓶颈,b)获得理想的应用程序“运行”的可视化表示,c)提供正在执行的并发行为的可视性,d)获取可用于评估您的更改之间差异的计时数据系统(如果您拥有强大的SLA,则非常重要)。

但是,分布式跟踪的后果是:

  1. 它增加了所有并发进程的开销,因为它转化为更多的代码以可能通过网络执行和提交。在某些情况下,这种开销非常重要-甚至Google都仅对所有请求的一小部分使用跟踪系统Dapper,以免破坏用户体验。

  2. 存在许多不同的工具,并非所有工具都可以互操作。OpenTracing之类的标准对此有所改善,但并未完全解决。

  3. 没有告诉您共享资源及其当前状态的任何信息。您可能可以根据应用程序代码和所看到的图形向您猜测,但这在这方面不是有用的工具。

  4. 当前的工具假定您有足够的内存和存储空间。托管时间序列服务器可能并不便宜,具体取决于您的限制。

错误跟踪软件

我之所以链接到上面的Sentry,主要是因为它是其中使用最广泛的工具,并且有充分的理由-诸如Sentry的错误跟踪软件会劫持运行时执行程序,以将遇到的错误的堆栈跟踪同时转发到中央服务器。

这种专用软件在并发代码中的净收益:

  1. 重复的错误不会重复。换句话说,如果一个或多个并发系统遇到相同的异常,Sentry将增加事件报告,但不提交事件的两个副本。

这意味着您可以找出哪个并发系统遇到哪种错误,而不必查看无数同时发生的错误报告。如果您曾经遭受过来自分布式系统的电子邮件垃圾邮件,那么您将知道到底是什么感觉。

您甚至可以“标记”并发系统的不同方面(尽管这假定您没有在一个线程上进行交错工作,从技术上讲,这并不是并发的,因为线程只是在任务之间高效地跳转,但仍必须处理事件处理程序完成),然后按标记查看错误细目。

  1. 您可以修改此错误处理软件,以提供有关运行时异常的更多详细信息。该流程有哪些开放资源?此过程是否拥有共享资源?哪个用户遇到了此问题?

除了细致的堆栈跟踪(和源映射,如果必须提供文件的缩小版本)之外,这还使得很容易确定大部分时间出了问题。

  1. (特定于哨兵的)您可以拥有一个单独的Sentry报告仪表板,用于系统的测试运行,从而使您能够捕获测试中的错误。

这种软件的缺点包括:

  1. 像所有东西一样,它们增加了体积。例如,您可能不希望在嵌入式硬件上使用这样的系统。我强烈建议您试用这种软件,将一个简单的执行与不执行的情况进行比较,并在空闲的计算机上对数百次的运行进行采样。

  2. 并非所有语言都受到同样的支持,因为许多系统都依赖于隐式捕获异常,并且并非所有语言都具有强大的异常。话虽这么说,但有很多系统的客户。

  3. 由于其中许多系统本质上是开源的,因此可能会带来安全风险。在这种情况下,请对它们进行尽职调查,或者,如果愿意,可以自己进行研究。

  4. 他们可能不会总是为您提供所需的信息。所有尝试增加可见性的操作都有风险。

  5. 这些服务大多数都是为高度并发的Web应用程序设计的,因此,并非每种工具都适合您的用例。

总而言之:具有可见性是任何并发系统中最关键的部分。我上面描述的两种方法,结合有关硬件和数据的专用仪表板以在任何给定时间点获得系统的整体图景,在整个行业中被广泛使用,以解决该问题。

一些其他建议

对于那些试图以可怕的方式解决并发问题的人,我花了更多的时间在修理代码上。每次我发现以下情况可以极大改善开发人员体验(与用户体验同等重要)的情况:

  • 依靠类型。存在键入以验证您的代码,并且可以在运行时用作额外的保护措施。在不存在类型的地方,依靠断言和合适的错误处理程序来捕获错误。并发代码需要防御性代码,而类型则是可用的最佳验证类型。

    • 测试代码组件之间的链接,而不仅仅是组件本身。请勿将其与功能全面的集成测试相混淆-该集成测试将测试每个组件之间的每个链接,即使这样,它也仅会寻找最终状态的全局验证。这是一种捕获错误的可怕方法。

一个好的链接测试会检查一个组件独立地与另一个组件通信,是否收到的消息和发送的消息是否与您期望的相同。如果您有两个或多个组件依赖共享服务进行通信,请将它们全部旋转,让它们通过中央服务交换消息,然后查看它们是否最终都能达到您的期望。

将涉及很多组件的测试分解为组件本身的测试以及每个组件之间如何通信的测试,可以使您对代码的有效性更有信心。进行如此严格的测试可以使您在服务之间强制执行合同,并捕获在一次运行时发生的意外错误。

  • 使用正确的算法来验证您的应用程序状态。我说的是简单的事情,例如,当您有一个主流程在等待所有工作人员完成一项任务,并且只想在所有工作人员都已完成时才移至下一步-这是检测全局问题的一个示例终止,存在诸如Safra算法的已知方法。

其中一些工具与语言捆绑在一起-例如,Rust保证您的代码在编译时不会出现竞争条件,而Go则具有一个内置的死锁检测器,该死锁检测器也在编译时运行。如果您能在问题投产之前就发现问题,那将是双赢。

一般经验法则:并发系统中的故障设计。预期公共服务将崩溃或中断。这甚至适用于不在计算机之间分发的代码-单台计算机上的并发代码可以依赖于外部依赖关系(例如共享日志文件,Redis服务器,该死的MySQL服务器),该依赖关系随时可能消失或被删除。 。

最好的方法是不时验证应用程序状态-对每个服务进行运行状况检查,并确保该服务的使用者收到不良运行状况的通知。诸如Docker之类的现代容器工具可以很好地做到这一点,应利用它来沙盒化。

您如何找出可以同时进行的事情和可以顺次进行的事情?

我在高度并发的系统上学习到的最大的经验教训之一是:您永远无法拥有足够的指标。指标绝对可以驱动应用程序中的所有内容-如果您不衡量所有内容,则您不是工程师。

没有指标,您将无法做一些非常重要的事情:

  1. 评估由于系统更改而造成的差异。如果您不知道调节旋钮A是否使指标B上升而指标C下降,那么当人们意外地将恶意代码推入系统时,您将不知道如何修复系统(他们会将代码推入系统) 。

  2. 了解下一步需要做些什么来改进它。在知道应用程序的内存不足之前,您无法确定是应该获得更多的内存还是为服务器购买更多的磁盘。

指标是如此关键和必要,以至于我在考虑系统需求之前就做出了有计划的计划。实际上,度量标准是如此重要,以至于我相信它们是该问题的正确答案:只有当您测量程序中的位在做什么时,您才知道可以使顺序或并发。正确的设计使用数字,而不是猜测。

话虽如此,当然有一些经验法则:

  1. 顺序意味着依赖。如果一个过程以某种方式依赖于另一个过程,则两个过程应该是顺序的。没有依赖关系的进程应该是并发的。但是,计划一种处理上游故障的方法,该方法不会阻止下游进程无限期地等待。

  2. 切勿将I / O绑定的任务与CPU绑定的任务混合在同一内核上。不要(例如)编写一个Web爬网程序,该爬网程序在同一线程中启动十个并发请求,在它们进入时立即将其抓取,并期望扩展到五百个-I / O请求并行进入队列,但是CPU仍然会顺序通过它们。(此单线程事件驱动模型是一种流行的模型,但由于这一方面而受到限制-人们不理解这一点,只是简单地绞尽脑汁,说Node无法扩展,为您提供示例)。

一个线程可以完成很多I / O工作。但是为了充分利用硬件的并发性,请使用一起占据所有内核的线程池。在上面的示例中,仅用于CPU工作的启动五个Python进程(每个进程可以使用六核计算机上的一个内核)和仅用于I / O工作的第六个Python线程将以比您想象的更快的速度扩展。

利用CPU并发性的唯一方法是通过专用线程池。一个线程通常足以应付许多I / O绑定工作。这就是为什么像Nginx这样的事件驱动的Web服务器可以更好地扩展(它们仅做I / O绑定工作)(将Apache I / O绑定工作与需要CPU并根据请求启动进程的对象进行扩展)的原因,但是为什么要使用Node来执行并行接收成千上万的GPU计算是一个糟糕的主意。


0

好吧,对于验证过程,当设计一个大型并发系统时,我倾向于使用LTSA-Labeled Transition System Analyzer来测试模型。它是由我的老老师开发的,他是并发领域的资深人士,现在是Imperial的计算主管。

至于确定哪些可以并发,哪些不可以并发,我相信有些静态分析器可以证明这一点,尽管我倾向于只为关键部分绘制计划图,就像您为项目管理一样。然后确定重复执行相同操作的部分。一种快速方法就是找到循环,因为它们往往是受益于并行处理的领域。


0

您如何确定可以同时进行的和必须进行顺序的?

您编写的几乎所有内容都可以利用并发优势,尤其是“确定征服”用例。更好的问题是应该并发什么?

Joseph Albahari的C#线程列出了五个常见用法。

多线程有很多用途。这是最常见的:

维护响应式用户界面

通过在并行的“工作者”线程上运行耗时的任务,主UI线程可以自由继续处理键盘和鼠标事件。

有效利用原本被阻塞的CPU

当线程正在等待来自另一台计算机或硬件的响应时,多线程很有用。当一个线程在执行任务时被阻塞时,其他线程可以利用本来没有负担的计算机。

并行编程

如果以“分而治之”策略在多个线程之间共享工作负载,则执行密集计算的代码可以在多核或多处理器计算机上更快地执行(请参阅第5部分)。

投机执行

在多核计算机上,有时可以通过预测可能需要完成的事情然后提前进行来提高性能。LINQPad使用此技术来加快新查询的创建。一种变体是并行运行许多不同的算法,它们全部解决同一任务。谁先获得“胜利” —当您不知道哪种算法执行最快时,这将很有效。

允许同时处理请求

在服务器上,客户端请求可以同时到达,因此需要并行处理(如果使用ASP.NET,WCF,Web服务或远程处理,.NET Framework会为此自动创建线程)。这在客户端上也很有用(例如,处理对等网络,甚至来自用户的多个请求)。

如果您不尝试执行上述任一操作,则最好认真考虑一下。

您如何重现错误条件并查看应用程序执行时发生的情况?

如果您使用的是.NET,并且已编写了用例,则可以使用CHESS,它可以重新创建特定的线程交错条件,从而使您能够测试修复程序。

您如何可视化应用程序的不同并发部分之间的交互?

这取决于上下文。对于工作人员场景,我认为是经理下属。经理告诉下属做某事并等待状态更新。

对于并发无关的任务,我认为电梯或轿厢位于不同的行车道中。

为了同步,我有时会想到交通灯或转向灯。

另外,如果您使用的是C#4.0,则可能需要看一下Task Parallel Library


0

我对这个问题的回答是:

  • 您如何确定可以同时进行的和必须进行顺序的?

首先,我需要知道为什么要使用并发,因为我发现人们对并发背后的想法已经退出了,但并不总是考虑他们要解决的问题。

如果必须模拟队列,工作流等现实情况,则很可能需要使用并发方法。

既然我知道我应该使用它,它就可以分析权衡了,如果您有很多过程,您可能会考虑通信开销,但是如果必须重新考虑,可能最终没有并发解决方案(所以。)

  • 您如何重现错误条件并查看应用程序执行时发生的情况?

我不是这方面的专家,但我认为对于并发系统,这不是正确的方法。应该选择一种理论方法,以查找关键区域的4个死锁要求:

  1. 非先发制人
  2. 保持并等待
  3. 动机排斥
  4. 圆链

    • 您如何可视化应用程序的不同并发部分之间的交互?

我尝试首先确定谁是交互的参与者,然后确定他们如何交流以及与谁交流。最后,图形和交互图帮助我形象化。我的旧白板很好,不能被其他任何媒体打败。


0

我会直率的。我喜欢工具。我使用很多工具。我的第一步是为状态流设计预期的路径。我的下一步是尝试找出是否值得,或者所需的信息流是否会使代码序列化过于频繁。然后,我将尝试起草一些简单的模型。这些范围可以从一堆原始的牙签雕塑到python中一些简单的相似示例。接下来,我浏览几本我最喜欢的书,例如那本关于信号量的小书,看看是否有人已经为我的问题找到了更好的解决方案。

然后我开始编码。
开玩笑。首先需要更多研究。我喜欢和其他黑客一起坐下来,并在较高的层次上逐步完成程序的预期执行。如果出现问题,我们将走下一个台阶。重要的是要找出其他人是否能够充分理解您的解决方案以进行维护。

最后,我开始编码。我首先尝试使其非常简单。只是代码路径,没有花哨。尽可能少地移动状态。避免写。避免可能与写入冲突的读取。首先要避免可能与写入冲突的写入。可以很容易地发现其中有一些具有正毒性的数字,而且您精美的解决方案突然变得比高速缓存颠覆性串行方法还多。

一个好的规则是尽可能使用框架。如果您自己编写基本的线程组件,例如良好的同步数据结构,或者天生就禁止使用实际的同步基元,那么几乎可以肯定,您将全力以赴。

最后是工具。调试非常困难。我在linux上将valgrind \ callgrind与PIN结合使用,在Windows上使用并行工作室。不要尝试手工调试这些东西。您可能可以。但您可能希望自己没有。十个小时掌握一些强大的工具和一些好的模型将为您节省数百小时。

最重要的是,逐步工作。小心点 厌倦时不要编写并发代码。请勿在饥饿时写它。实际上,如果可以避免,那就不要编写它。并发很难,而且我发现许多将它列为一项功能的应用程序经常将其作为其唯一功能。

总结:
开始:
思考
谈话
测试
只是编写
阅读
测试
编写
调试
转到开始

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.