【问题标题】:Why is lockless concurrency such a big deal (in Clojure)?为什么无锁并发如此重要(在 Clojure 中)?
【发布时间】:2010-11-24 13:08:46
【问题描述】:

有人告诉我 Clojure 具有无锁并发,这很重要。

我使用过多种语言,但没有意识到它们在幕后执行锁定。

为什么这是 Clojure(或任何具有此功能的语言)的优势?

【问题讨论】:

    标签: concurrency clojure deadlock lock-free


    【解决方案1】:

    无锁并发还提供了一个很好的优势,即读取器永远不必等待其他读取器。当许多线程将从单个源读取数据时,这尤其有用。您仍然需要在程序中定义数据依赖关系,并明确定义可以安全交换的事务部分。
    STM 使您免于死锁和几乎所有活锁的发生,尽管它不能使您免于并发故障,您仍然可以创建事务失败的情况,因为它缺乏维护其历史的资源,但重要的部分是 并发故障将是明确的,您可以从中恢复

    【讨论】:

    • 是的! STM = 软件事务内存。如果您想了解更多关于它是如何工作的,这是一项很好的工作。
    • 在 Clojure STM 中,读取器甚至不必等待其他写入器(除非他们想要进行事务读取以获得跨多个引用的一致快照)
    • readers never have to wait for other readers 在命令式世界中,读写锁提供了这个功能:)
    【解决方案2】:

    我不能具体谈论 Clojure,但是……这意味着您无需等待某人完成某事才能开始工作。太棒了。

    通常它是通过不可变类型实现的。如果没有任何东西可以修改,那么您实际上不需要等到其他人完成它才能访问它。

    【讨论】:

    • “某人”是谁?你能举个例子吗?我不认为有任何“有人与我分享我的物品”。也许我应该检查一下床底下。
    • someone 只是在另一个线程中访问该对象的另一段代码。
    • 有时更快有时更慢 - 就像大多数没有测量的优化一样。所以有时这是一件好事,有时那些锁会更好。这就是我们被称为软件工程师的原因。
    • 请注意,Clojure 通过结合不可变数据结构和可变“托管引用”来实现无锁并发。见:infoq.com/presentations/Value-Identity-State-Rich-Hickey
    【解决方案3】:

    死锁。或者更正确地说是缺少它们。

    大多数语言的最大问题之一是您最终会遇到以下死锁:

    1. 地狱般的调试。
    2. 很难确定你已经摆脱了。

    现在没有锁,显然你不会遇到死锁。

    【讨论】:

    • 我从未遇到过我知道的死锁。它是否比我意识到的更频繁地发生?我想我只是不熟悉这个问题,即使我已经编写了很多程序。我知道的唯一锁定问题是在数据库中。
    • @i_like_monkeys 如果您使用 C# 编写 Web 前端,那么大多数锁定问题将在较低层(Web 服务器和数据库),但如果您要开发的程序是绝对是个问题。
    • 如果您遇到死锁,症状通常是您的程序死机(0% CPU 使用率,它的任何任务都没有完成,再一次,直到您使用任务管理器等手动杀死它)。如果您的线程一次持有多个锁,并且不同的线程以不同的顺序获取锁,则可能会发生死锁。
    【解决方案4】:

    最大的问题是锁不构成。

    虽然使用简单的锁定策略编写代码(例如,将其放在同步的 Java 类中......)是微不足道的,但当您开始锁定多个对象并开始创建复杂的事务时,它会变得更加复杂。结合不同的锁定操作。可能会发生死锁,性能受损,锁定逻辑开始使代码变得极其复杂,并且在某些时候代码开始变得不可维护。

    对于必须构建大型复杂并发系统的任何人来说,这些问题都会变得显而易见(解决这些问题是 Rich Hickey 创建 Clojure 的主要动机)。

    第二个问题是性能。

    锁定和 STM 都明显增加了开销。但在某些重要情况下,STM 开销可能会低得多。

    特别是,无锁并发(与 Clojure STM 一样)通常意味着如果读取器在事务之外访问数据,则不会受到任何其他线程(包括写入器!)的影响。在相当常见的情况下,这可能是一个巨大的胜利,读取不需要是事务性的并且大大超过写入(想想大多数 Web 应用程序......)。 Clojure 中对 STM 引用的非事务性读取基本上没有开销。

    【讨论】:

    • Non-transactional reads of an STM reference in Clojure are essentially overhead free. 原则上事务性读取也应该如此? Doc 说“读者和通勤者永远不会阻止作家、通勤者或其他读者。”
    【解决方案5】:

    只要您编写严格顺序的程序(执行 A,然后 B,然后 C;完成!),您就不会有并发问题,并且语言的并发机制仍然无关紧要。

    当您从“编程练习”程序毕业到现实世界时,很快您就会遇到解决方案是多线程(或任何可用的并发)的问题。

    案例:带有 GUI 的程序。假设您正在编写带有拼写检查功能的编辑器。您希望拼写检查器在后台安静地执行它的操作,但您希望 GUI 顺利接受用户输入。因此,您将这两个活动作为单独的线程运行。

    案例:我最近编写了一个程序(用于工作),它从两个日志文件中收集统计信息并将它们写入数据库。处理每个文件大约需要 3 分钟。我将这些进程转移到两个并行运行的线程中,将总处理时间从 6 分钟缩短到 3 分钟多一点。

    案例:科学/工程仿真软件。通过计算代表您的测试对象(星核、核爆炸、昆虫种群的地理分布......)的 3 维网格中每个点的一些效应(比如热流),可以解决很多很多问题。基本上每个点和很多点都进行相同的计算,所以让它们并行完成是有意义的。

    在所有这些情况以及更多情况下,只要两个计算进程大致同时访问相同的内存(= 变量,如果您愿意的话),它们就有可能相互干扰并弄乱彼此的工作。处理“并发编程”的计算机科学的庞大分支处理有关如何解决此类问题的想法。

    可以在in Wikipedia 找到有关此主题的相当有用的开始讨论。

    【讨论】:

      【解决方案6】:

      无锁并发的好处是程序不复杂。在命令式语言中,并发编程依赖于锁,一旦程序变得相当复杂,难以修复的死锁错误就会蔓延。

      【讨论】:

      • 你遇到过这种情况吗?
      【解决方案7】:

      这种“无锁并发”实际上并不是一种语言的特性;相反,它是平台或运行时环境的一项功能,不幸的是,这种语言不会让您访问这些设施。

      考虑基于锁和无锁并发之间的交易类似于元循环求值器问题:一个可以用原子操作(例如比较和交换,或 CAS)来实现锁,一个可以实现原子锁方面的操作。哪个应该在底部?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-11-29
        • 2018-09-01
        • 2011-04-28
        • 1970-01-01
        • 2019-06-17
        • 1970-01-01
        • 2010-12-07
        相关资源
        最近更新 更多