我是一个相当不错的程序员,我的老板也是一个相当不错的程序员。尽管他似乎低估了一些任务,例如多线程以及它的难度(我发现除了运行几个线程,等待所有线程完成然后返回结果之外,其他事情非常困难)。

当您开始担心僵局和比赛条件时,我觉得这很困难,但老板似乎并不喜欢这一点-我认为他从未遇到过。只是一巴掌就可以解决问题。

那么我该怎么介绍他,或者解释一下为什么他可能低估了并发,并行和多线程的复杂性?或者,也许我错了?

编辑:只做了一点他的工作-遍历一个列表,为该列表中的每个项目创建一个线程,该线程根据其中的信息执行数据库更新命令。该项目。我不确定他如何控制一次执行多少个线程,我想如果运行太多(他不会使用信号量),他一定会将它们添加到队列中。

评论

多线程很容易。正确的同步很难。

带三个人进入房间,最好使用不同的口音,并让他们同时解释并发问题的重叠部分....

多线程处理可能非常困难或非常容易,具体取决于当前的问题和语言支持。 Clojure使操作变得容易clojure.org/concurrent_programming

@Job并发编程总是很困难(在现实世界的项目中),无论您使用哪种语言。当您要将Scala,Clojure或Erlang与使用并鼓励可变状态的语言进行比较时,它会显得有些理智。

我最喜欢的比喻是:“您会同时服用安眠药和泻药吗?”即使使用复杂的消息队列,顺序也是正确完成并发的结果。除非您有丰富的经验,否则这对许多人来说是很难的。

#1 楼


如果您可以依靠任何数学经验,请说明本质上是确定性的正常执行流程如何不仅会变得不确定并带有多个线程,而且会呈指数级复杂化,因为您必须确保对机器指令的所有可能的交织仍然可以完成正确的事情。一个简单的丢失更新或读取状况不佳的例子通常令人大开眼界。
“轻轻松松地锁定它”是不重要的解决方案……如果您不关心性能,它可以解决所有问题。尝试说明一下,例如,当亚特兰大某人订购一本书时,亚马逊不得不锁定整个东海岸,将会给性能造成多大的打击!


评论


+1讨论数学的复杂性-这就是我了解共享状态并发困难的方式,也是我在提倡消息传递体系结构时通常会提出的论点。 -1表示“对它拍上一个锁” ...该短语表示使用锁的一种思维方法,很可能会导致死锁或行为不一致(因为驻留在不同线程中的代码客户端会产生冲突)请求,但彼此之间不同步,则客户端将具有库状态的不兼容模型)。

–艾丹·库利(Aidan Cully)
2011年6月2日,11:04

亚马逊确实必须在处理订单时短暂地将单个物品的库存锁定在一个仓库中。如果某个特定项目突然发生大量运转,则该项目的订购性能将受到影响,直到供应用完并且对库存的访问变为只读(因此可以100%共享)。亚马逊追求的其他程序没有做到的一件事是能够在重新进货之前对订单进行排队,并且可以在将重新进货给新订单之前为已排队的订单提供服务。

– Blfl
2011-6-2 13:19



@Blrfl:如果程序被编写为使用通过队列传递消息,则可以做到这一点。无需使所有消息都通过单个队列传递给特定线程。

–研究员
2011年6月2日14:21

@Donal研究员:如果一个仓库中有100万个小部件库存,并且100万个订单是在同一时刻到达的,那么所有这些请求都将在某种程度上进行序列化,而无论商品如何与订单匹配。实际的现实情况是,Amazon可能从来没有太多的小部件库存,以致在库存耗尽之前处理大量订单的延迟变得令人无法接受,同时其他所有人都可以被告知(并行),“我们已经淘汰了。 ”消息队列是防止死锁的好方法,但是它们不能解决资源有限的高争用问题。

– Blfl
2011年6月2日15:22

#2 楼

多线程很简单。对应用程序进行多线程编码非常非常容易。

有一个简单的技巧,那就是使用设计良好的消息队列(不要自己滚动)在线程之间传递数据。

最困难的部分是尝试使多个线程以某种方式神奇地更新共享库。那时,它很容易出错,因为人们不注意当前的竞争条件。

许多人不使用消息队列并尝试更新共享对象并为自己创建问题。

困难的是设计一种在多个队列之间传递数据时能很好工作的算法。那很难。但是(通过共享队列)并存线程的机制很容易。

此外,请注意线程共享I / O资源。具有I / O约束的程序(即网络连接,文件操作或数据库操作)不可能在有很多线程的情况下更快地运行。

如果要说明共享库更新问题,那很简单。用一堆纸卡坐在桌子对面。写下一组简单的计算-4或6个简单的公式-页面下方还有很多空间。

这就是游戏。你们每个人都读一个公式,写一个答案,并在卡片上写下答案。

你们每个人都会做一半的工作,对不对?如果完成一半,您就完成了吗?

如果老板不怎么想而只是开始,您将以某种方式结束冲突,并且都针对相同的公式编写答案。那没有用,因为你们两个人在写作之前阅读之间存在固有的竞争条件。没有什么可以阻止您阅读相同的公式并覆盖彼此的答案。

有很多很多方法可以使用不良或未锁定的资源来创建竞争条件。

如果要避免所有冲突,请将纸张切成一堆公式。您无需排队,写下答案,然后发布答案。没有冲突,因为你们俩都从一个只读的消息队列中读取。

评论


即使将纸张切成一堆也不能完全解决问题-您仍然遇到这样的情况,您和您的老板会同时寻求一个新的公式,而您的拳头也会碰到。实际上,我想说这是最常见的线程问题的代表。真正的重大错误是尽早发现的。真正异常的错误永远存在,因为没有人可以复制它们。像这样的合理的比赛条件在测试中不断涌现,最终所有(或更可能是大多数)都被淘汰了。

–Airsource Ltd
15年12月14日在16:37

@AirsourceLtd您到底是在说“打着他的指关节”在说什么?只要您有一个阻止两个不同线程接收同一条消息的消息队列,就不会有问题。除非我误会了你的意思。

–扎克
16-09-23在16:51

#3 楼

多线程编程可能是并发最困难的解决方案。基本上,它只是对计算机实际功能的一个低级抽象。

有许多方法要容易得多,例如参与者模型或(软件)事务存储。或使用不可变的数据结构(例如列表和树)。

通常,适当的关注点分离会使多线程更容易。当人们产生20个线程时,所有人都试图处理同一个缓冲区时,这就是通常会忘记的事情。在需要同步的地方使用反应堆,并且通常在带有消息队列的不同工作人员之间传递数据。
如果您的应用程序逻辑有锁,那么您做错了什么。

所以是的,从技术上讲,多线程化很困难。
“锁上锁”几乎是解决并发性问题的最不可行的解决方案,实际上破坏了多线程的全部目的。它的作用是将问题恢复为非并行执行模型。执行的次数越多,一次运行的线程(或死锁中的0)的可能性就越大。这完全没有目的。
这就像在说:“解决第三世界的问题很容易。只需向上面扔炸弹。”
因为有一个简单的解决方案,但这不会使问题变得微不足道,因为您关心结果的质量。

但是在实践中,解决这些问题同样困难与其他任何编程问题一样,最好使用适当的抽象来完成。实际上,这相当容易。

#4 楼

我认为这个问题有一个非技术性的角度-IMO是一个信任的问题。我们通常被要求复制复杂的应用程序,例如-哦,我不知道-例如Facebook。我得出的结论是,如果您不得不向未开始的人员/管理人员解释任务的复杂性,那么丹麦的情况就糟透了。

即使其他忍者程序员可以在5分钟内完成任务,您的估算也要基于您的个人能力。您的对话者应该学会相信您对此事的意见,或者聘请愿意接受其话的人。

挑战不在于传达人们往往会忽略或无法做到的技术含义。通过交谈来把握,但要建立相互尊重的关系。

评论


有趣的答案,尽管这是一个技术问题。但是,我确实同意您的意思……虽然,在这种情况下,我的经理是一位非常出色的程序员,但是我只是认为,因为他没有遇到多线程应用程序的复杂性,所以他低估了它们。

– Shoubs先生
2011年6月2日15:38

#5 楼

理解僵局的一个简单的思想实验就是“吃饭的哲学家”问题。我倾向于用来描述恶劣的比赛条件的例子之一是Therac 25的情况。

“只要轻拍一下它的锁”就是一个没有遇到困难的人的心态。多线程错误。而且他可能认为您夸大了这种情况的严重性(我不是-可能会炸毁东西或杀死带有种族状况错误的人,尤其是最终嵌入汽车的嵌入式软件)。

评论


就是三明治问题:您要做成一堆三明治,但是只有一盘黄油和一把刀。通常情况很好,但最终有人会抓住黄油,而别人会抓住刀子..然后他们俩都站在那儿等着对方放开他们的资源。

– gbjbaanb
2011年6月2日在20:24

这样的僵局问题是否可以通过始终按固定顺序获取资源来解决?

– compman
2011年6月11日19:15

@compman,不。因为两个线程可以同时尝试获取相同的资源,并且这些线程不一定需要相同的资源集-只是重叠而足以引起问题。一种方案是将资源“放回”,然后等待一个随机的时间段,然后再次获取它。此退避期发生在许多协议中,最早的是Aloha。 en.wikipedia.org/wiki/ALOHAnet

–坦古雷纳
2011年6月11日20:29

如果程序中的每个资源都有一个数字,并且当线程/进程需要一组资源时,它总是以递增的数字顺序锁定资源怎么办?我认为不会发生僵局。

– compman
2011年6月11日22:02

@compman:这确实是避免死锁的一种方法。可以设计工具来让您自动检查此情况。因此,如果从未发现您的应用程序以递增的数字顺序锁定资源,那么您就永远不会有潜在的死锁。 (请注意,只有当您的代码在客户的计算机上运行时,潜在的死锁才会变成真正的死锁)。

– gnasher729
15年8月23日在15:31

#6 楼

简短的回答有两个词:可观察的不确定性

长回答:
取决于给定问题时使用哪种并行编程方法。在《计算机程序设计的概念,技术和模型》一书中,作者清楚地解释了编写并发程序的四种主要实用方法:




顺序编程:没有任何基础方法并发;

声明式并发:在没有可观察到的不确定性时可用;

消息传递并发:在许多实体之间传递并发消息,其中每个实体在内部按顺序处理消息;

共享状态并发:线程通过粗粒度原子操作来更新共享的被动对象,例如锁,监视器和事务;

现在,除了明显的顺序编程之外,这四种方法中最简单的是声明式并发,因为使用这种方法编写的程序没有明显的不确定性。换句话说,没有竞争条件,因为竞争条件只是可观察的不确定性行为。

,但是缺乏可观察的不确定性意味着,存在一些我们无法使用声明式并发解决的问题。这是最后两种不太容易的方法起作用的地方。不太容易的部分是可观察的不确定性的结果。现在它们都属于有状态并发模型,并且在表达能力上也等效。但是,由于每个CPU内核数量的不断增加,最近业界似乎对消息传递并发越来越感兴趣,这可以从消息传递库(例如JVM的Akka)或编程语言(例如Erlang)的兴起中看出。

前面提到的Akka库(由理论上的Actor模型支持)简化了并发应用程序的构建,因为您不必再​​处理锁,监视器或事务。另一方面,它需要不同的方法来设计解决方案,即以某种方式思考如何分层组合参与者。可以说这需要一种完全不同的心态,这最终可能比使用纯状态共享并发还要困难。

由于可观察到的不确定性,并发编程很困难,但是如果使用正确的方法对于给定的问题以及支持该方法的正确库,则可以避免很多问题。

#7 楼

并发应用程序不是确定性的。由于程序员认为极易受攻击的总体代码量非常少,因此您无法控制线程/进程的一部分相对于另一个线程的任何部分何时执行。测试更加困难,需要更长的时间,并且不太可能找到所有与并发相关的缺陷。如果发现缺陷,则无法始终再现其细微之处,因此很难修复。

因此,唯一正确的并发应用程序是经证明正确的应用程序,这在软件开发中并不经常实践。因此,S.Lot的答案是最好的一般建议,因为消息传递相对容易证明是正确的。

#8 楼

我首先被告知,它可以通过看到一个简单的程序启动2个线程,并同时将它们同时从1-100打印到控制台,从而引发问题。代替:

1
1
2
2
3
3
...


您会得到更多类似的信息:

1
2
1
3
2
3
...


再次运行它,您可能会发现得到完全不同的结果。

我们大多数人都已经接受过训练,假设我们的代码将按顺序执行。对于大多数多线程,我们不能认为这是“开箱即用”的。

#9 楼

尝试一次使用多个锤子将一堆紧密间隔的钉子捣成糊状,而握住锤子的人之间没有任何联系……(假设它们被蒙住了眼睛)。

将其升级为盖房。

现在想在晚上睡觉,想象你是建筑师。 :)

#10 楼

简单的部分:使用具有框架,操作系统和硬件的最新功能的多线程,例如信号灯,队列,互锁的计数器,原子盒式类型等。

硬部分:首先不使用任何功能来实现自己的功能,可能只是少数硬件功能非常有限,仅依赖于跨多个内核的时钟一致性保证。

评论


困难的部分确实更难,但即使是简单的部分也不是那么容易。

– PeterAllenWebb
2011年6月2日在20:23