确保线程安全的最佳编程方法/方法

时间:2008-11-02 04:45:33

标签: multithreading multicore concurrency

当我从基础,Pascal,COBOL和C的大约20年的程序编程背景中学习Java时,我当时认为最困难的事情是围绕OOP术语和概念。现在我已经掌握了大约8年的扎实Java,我得出的结论是,使用Java编程和C#等类似语言最难的是多线程/并发方面。

编写可靠且可扩展的多线程应用程序非常困难!随着处理器变得“更宽”而不是更快的趋势,它正迅速变得非常关键。

当然,最困难的领域是控制线程之间的交互以及由此产生的错误:死锁,竞争条件,陈旧数据和延迟。

所以我的问题是:采用什么方法或方法来生成安全的并发代码,同时减少死锁,延迟和其他问题的可能性?我提出了一种有点非常规的方法,但在几个大型应用程序中运行良好,我将在这个问题的详细答案中分享。

15 个答案:

答案 0 :(得分:7)

这不仅适用于Java,也适用于一般的线程编程。我发现自己只是遵循以下准则来避免大多数并发和延迟问题:

1 /让每个线程运行自己的生命周期(即,决定何时死亡)。它可以从外部提示(比如一个标志变量),但它完全负责。

2 /让所有线程以相同的顺序分配和释放资源 - 这可以保证不会发生死锁。

3 /尽可能短的时间锁定资源。

4 /通过数据本身对数据负责 - 一旦您通知线程数据是要处理的数据,不管它,直到责任交还给您。

答案 1 :(得分:6)

现在有很多技术进入公众意识(如:过去几年)。一个重要的是演员。这是Erlang首先引入网格的东西,但是由Scala等新语言(JVM上的演员)推进。虽然演员确实没有解决所有问题,但他们确实使 更容易推理您的代码并找出问题点。它们还使设计并行算法变得更加简单,因为它们强制您使用连续传递共享可变状态的方式。

Fork / Join是你应该看的东西,特别是如果你在JVM上。 Doug Lea撰写了关于该主题的开创性论文,但许多研究人员多年来一直在讨论它。据我了解,Doug Lea的参考框架计划包含在Java 7中。

在略微侵入性较低的级别上,通常简化多线程应用程序所需的唯一步骤只是降低锁定的复杂性。细粒度锁定(Java 5风格)非常适合吞吐量,但很难做到正确。锁定的一种替代方法是通过Clojure获得一些牵引力的是软件事务存储器(STM)。这基本上与传统锁定相反,因为它是乐观的而不是悲观的。首先假设您不会发生任何冲突,然后允许框架在问题发生时解决问题。数据库通常以这种方式工作。它对于具有低冲突率的系统的吞吐量非常有用,但最大的好处在于算法的逻辑组件化。您可以将危险代码包装在事务中,让框架弄清楚其余部分,而不是任意地将锁(或一系列锁)与某些数据相关联。您甚至可以通过GHC的STM monad或我的实验性Scala STM获得相当多的编译时间检查。

构建并发应用程序有很多新选项,您选择的应用程序很大程度上取决于您的专业知识,语言以及您尝试建模的问题类型。作为一般规则,我认为演员加上持久的,不可变的数据结构是一个坚实的赌注,但正如我所说,STM的侵入性稍差,有时可以产生更多的立即改进。

答案 2 :(得分:5)

  1. 尽可能避免在线程之间共享数据(复制所有内容)。
  2. 在可能的情况下,永远不要锁定对外部对象的方法调用。
  3. 尽可能在最短的时间内保持锁定。

答案 3 :(得分:5)

Java中的线程安全没有 One True Answer 。但是,至少有一本非常好的书:Java Concurrency in Practice。我经常参考它(尤其是我旅行时的在线Safari版本)。

我强烈建议您仔细阅读本书。您可能会发现深入研究非常规方法的成本和收益。

答案 4 :(得分:4)

我通常遵循Erlang风格的方法。我使用活动对象模式。 它的工作原理如下。

将您的应用程序划分为非常粗糙的单位。在我目前的一个应用程序(400.000 LOC)中我有appr。这些粗粒单元中有8个。 这些单元根本不共享任何数据。每个单位都保留自己的本地数据。每个单元都在自己的线程上运行(= Active Object Pattern),因此是单线程的。单位内不需要任何锁。当单位需要向其他单位发送消息时,他们通过将消息发布到其他单位的队列来进行。另一个单元从队列中选择消息并对该消息作出反应。这可能会触发其他单元的其他消息。 因此,此类应用程序中的唯一锁定在队列周围(一个队列和每个单元的锁定)。 根据定义,此架构无死锁!

这种体系结构非常好地扩展,只要您理解了基本原理,就很容易实现和扩展。它喜欢将它视为应用程序中的SOA。

将您的应用划分为单位记住。每个CPU核心的最佳长线程数为1。

答案 5 :(得分:3)

我推荐基于流的编程,即数据流编程。它使用 OOP和线程,我觉得这是一个自然的前进,就像OOP是程序性的。不得不说,数据流编程不能用于一切,它不是通用的。

维基百科在这个主题上有很好的艺术效果:

http://en.wikipedia.org/wiki/Dataflow_programming

http://en.wikipedia.org/wiki/Flow-based_programming

此外,它有几个优点,因为令人难以置信的flexibile配置,分层;程序员(组件程序员)不必编程业务逻辑,它是在另一个阶段完成的(将处理网络放在一起)。

您知道吗, make 是一个数据流系统?请参阅 make -j ,特别是如果您有多核处理器。

答案 6 :(得分:0)

非常谨慎地编写多线程应用程序中的所有代码!我不知道有什么更好的答案。 (这涉及到jonnii提到的内容)。

我听说人们争论(并同意他们)传统的线程模型真的不会进入未来,因此我们将不得不开发一套不同的范例/语言来真正使用这些有效的新奇多核。像Haskell这样的语言,其程序很容易并行化,因为任何具有副作用的函数都必须以这种方式明确标记,而Erlang,我很遗憾不太了解。

答案 7 :(得分:0)

答案 8 :(得分:0)

我建议演员模特。

答案 9 :(得分:0)

actor model就是您正在使用的,它是迄今为止多线程内容最简单(最有效的方法)。基本上每个线程都有一个(同步)队列(它可以是OS依赖或不依赖),其他线程生成消息并将它们放在将处理消息的线程的队列中。

基本示例:

thread1_proc() {

msg = get_queue1_msg(); // block until message is put to queue1
threat1_msg(msg);

}

thread2_proc() {
msg = create_msg_for_thread1();
send_to_queue1(msg);
}

这是producer consumer问题的一个典型例子。

答案 10 :(得分:0)

这显然是一个难题。除了显而易见的谨慎之外,我认为第一步是准确定义您需要的线程和原因。

设计线程就像设计类一样:确保知道是什么使它们保持一致:它们的内容以及它们与其他线程的交互。

答案 11 :(得分:0)

我记得有点震惊的是发现Java的synchronizedList类不是完全线程安全的,而只是有条件地线程安全的。如果我没有在同步块中包装我的访问(迭代器,设置器等),我仍然会被烧毁。这意味着我可能向我的团队和管理层保证我的代码是线程安全的,但我可能错了。我可以确保线程安全的另一种方法是使用工具来分析代码并使其通过。 STP,演员模型,Erlang等是获得后一种形式保证的一些方法。能够可靠地确保程序的属性将是/将是编程中的一大进步。

答案 12 :(得分:0)

看起来你的IOC有点像FBP :-)如果JavaFBP代码可以从像你这样精通编写线程安全代码的人那里得到彻底的审查那就太棒了......这是在SourceForge的SVN上

答案 13 :(得分:0)

有些专家认为你的问题的答案是完全避免线索,因为几乎不可能避免不可预见的问题。引用The Problem with Threads

我们开发了一个包含的流程 代码成熟度评级系统(有四个级别,红色,黄色,绿色和蓝色),设计评论,代码 评论,夜间构建,回归测试和自动代码覆盖度量标准。部分 在2000年初编写了确保程序结构一致视图的内核, 设计审核为黄色,代码审核为绿色。审稿人包括并发专家, 不仅仅是没有经验的研究生(Christopher Hylands(现为Brooks),Bart Kienhuis,John Reekie和[Ed Lee]都是评论家。我们编写了回归测试,实现了100%的代码 报道... ......系统本身开始被广泛使用,系统的每次使用都运用了这一点 码。直到2004年4月26日四年后代码陷入僵局,才发现问题。

答案 14 :(得分:0)

使用多线程设计新应用程序的最安全方法是遵守以下规则:

设计下面没有设计。

这是什么意思?

想象一下,您确定了应用程序的主要构建块。让它成为GUI,一些计算引擎。通常情况下,一旦你拥有足够大的团队规模,团队中的一些人就会要求"图书馆"到"分享代码"在这些主要构建块之间。虽然在开始时为主要构建块定义线程和协作规则相对容易,但所有这些努力现在都处于危险之中,因为代码重用库"将被设计得很糟糕,在需要的时候进行设计,并且充满了锁和互斥锁,这些锁和互斥锁感觉正确"。 这些特殊库是您设计之下的设计,也是您的线程架构的主要风险。

该怎么办?

  • 告诉他们你的代码重复比跨线程边界的共享代码更重要。
  • 如果您认为,该项目将真正受益于一些图书馆,建立他们必须无国家和可重入的规则。
  • 您的设计正在不断发展,其中包括一些常见代码"可能会向上移动"在设计中成为您应用程序的新主要构建块。
  • 远离酷酷的图书馆网络狂热。一些第三方库可以真正为您节省大量时间。但也有一种倾向,任何人都有他们的最爱",这几乎不是必需的。随着您添加的每个第三方库,您遇到线程问题的风险也会增加。

最后,请考虑在主要构建块之间进行基于消息的交互;例如,参见经常提到的演员模型。