代码之家  ›  专栏  ›  技术社区  ›  Brig Lamoreaux

常见的并发陷阱是什么[[关闭]

  •  30
  • Brig Lamoreaux  · 技术社区  · 16 年前

    我正在研究如何对我们的团队进行并发性教育。开发人员在并发环境中最常见的陷阱是什么。例如,在.Net中 static 打开了许多并发问题的大门。

    更新

    21 回复  |  直到 13 年前
        1
  •  19
  •   Die in Sente    16 年前

    在这个问题上已经有很多很好的答案和指针了,但是让我补充一点。

    不要依赖测试来发现竞争条件和死锁

    假设您已经准备好了所有良好的开发过程:每个组件的单元测试、每个夜间构建的冒烟测试、要求每个开发人员的更改在签入前通过测试等等。

    所有这些都是好的,但是它导致了一种“好吧,它通过了测试套件,所以它不可能是我代码中的bug”的态度,这在并发编程中并不能很好地为您服务。实时并发错误很难重现。在一段代码失败之前,你可以用一个竞态条件运行它10亿次。

    您将不得不调整您的流程,以便更加重视由您的最佳头脑进行的代码评审。仅针对并发性问题进行单独的代码检查并不是一个坏主意。

    您必须更加强调使应用程序能够自我调试。也就是说,当您在测试实验室或客户站点遇到故障时,您需要确保捕获并记录了足够的信息,以便进行最终的验尸,因为您能够在方便的时候复制错误报告的可能性可以忽略不计。

    您必须更加强调代码中的偏执健全性检查,以便在尽可能接近问题的地方检测到错误,而不是在50000行代码之外。

    多疑。非常偏执。

        2
  •  16
  •   Serhat Ozgel    16 年前

    一个是 race condition ,基本上是假设一段代码将在另一段并发代码之前/之后运行。

    还有 deadlocks

        3
  •  13
  •   Engineer    8 年前

    我教我的朋友和同事很多并发性。以下是一些大的陷阱:

    • 假设一个主要在多个线程中读取并且只在一个线程中写入的变量不需要被锁定(在Java中,这种情况可能会导致读取线程永远看不到新值。)
    • 假设线程将按特定顺序运行。
    • 假设线程将同时运行。
    • 假设线程不会同时运行。

    我还看到:

    • 两者之间的巨大混淆 thread_fork() fork()
    • 在一个线程中分配内存和 free() 在另一个线程中。
        4
  •  12
  •   James McMahon    16 年前

    最大的陷阱之一是首先使用并发。并发性增加了大量的设计/调试开销,因此您必须检查问题,看看它是否真的需要并发性。

    在某些领域,并发当然是不可避免的,但是当它是可以避免的时候,就要避免它。

        5
  •  11
  •   Maurice Flanagan    16 年前

    然而,同步对共享数据的访问是很棘手的。

    以下是任何编写共享数据同步代码的人都应该能够回答的一些问题:

    1. 为什么联锁增量需要存在于汇编语言级别?
    2. 什么是读写重新排序?
    3. volatile关键字是什么(在c++中)以及何时需要使用它?
    4. 什么是同步层次结构?
    5. 什么是ABA问题?
    6. 什么是缓存一致性?
    7. 什么是记忆障碍?

    shared nothing message passing 相反。

        6
  •  4
  •   Dunk    16 年前

    要记住的一个事实是,即使最初的开发人员让他们的任务分配模型正常工作(这是一个很大的if),那么接下来的维护团队肯定会以难以想象的方式把事情搞砸。这样做的好处是限制整个系统中并发的痕迹。尽你最大的努力确保你的系统大部分都没有意识到并发正在发生。这就减少了不熟悉任务分配模式的人不经意间把事情搞砸的机会。

    人们常常对线程/任务发狂。每件事都有自己的思路。最终的结果是,几乎每一段代码都必须密切注意线程问题。它迫使原本简单的代码充斥着锁定和同步混淆。每次我看到这一点,系统最终都会变成一个无法维护的烂摊子。然而,每次我看到这一点,最初的开发人员仍然坚持认为这是一个很好的设计:(

    就像多重继承一样,如果你想创建一个新的线程/任务,那么在证明你错了之前就假设你错了。我甚至无法计算我看到模式线程A调用线程B,然后线程B调用线程C,然后线程C调用D的次数,所有这些都在等待前一个线程的响应。所有的代码都是通过不同的线程进行冗长的函数调用。当函数调用工作正常时不要使用线程。

    我发现创建一个能够处理几乎所有并发问题的核心基础设施效果最好。如果核心基础设施之外有任何线程必须与另一个软件进行通信,那么它们必须通过核心基础设施。这样系统的其余部分就可以保持不知道并发性,并发性问题可以由希望理解并发性的人来处理。

        7
  •  3
  •   DJClayworth    16 年前

    如其他答案所述,两个最可能的问题是 竟态条件 . 不过,我的主要建议是,如果您希望培训一个关于并发主题的团队,我强烈建议您 你自己训练一下 . 找一本关于这个主题的好书,不要依赖网站上的几段话。一本好书取决于您所使用的语言:Brian Goetz的《Java并发实践》适合于该语言,但是还有很多其他的书。

        8
  •  3
  •   Bruce ONeel    16 年前

    一切都归结为共享数据/共享状态。如果您不共享数据或状态,那么就没有并发问题。

    大多数人,当他们想到并发时,会想到单个进程中的多线程。

    考虑这一点的一种方法是,如果将流程拆分为多个流程,会发生什么情况。他们必须在哪里相互交流?如果你能清楚地知道这些进程在哪里必须相互通信,那么你就对它们共享的数据有了一个很好的了解。

    (剩下的部分不适用于Java线程,我不使用它,因此对它知之甚少)。

    如果没有一个在实际系统中非常罕见的小心级别,您不可能使多个锁正常工作。

        9
  •  2
  •   Pankrat    16 年前

    根据我的经验,许多(熟练的)开发人员缺乏并发理论的基础知识。Tanenbaum或Stallings的经典操作系统教科书很好地解释了并发的理论和含义:互斥、同步、死锁和饥饿。良好的理论背景是成功使用并发的必要条件。

    也就是说,在不同的编程语言和不同的库之间,并发支持有很大的不同。此外,测试驱动开发在检测和解决并发问题方面并没有取得太大进展(尽管短暂的测试失败表明存在并发问题)。

        10
  •  2
  •   Zan Lynx    16 年前

    我所看到的第一个陷阱是太多的数据共享。

    我认为处理并发性的更好方法之一是多进程而不是线程。这样,线程/进程之间的通信严格限制在所选的管道、消息队列或其他通信方法中。

        11
  •  1
  •   xelurg    16 年前

    下面是一个关于并发的很好的资源,特别是在Java中: http://tech.puredanger.com/ alexmiller列出了处理并发时可能遇到的许多不同问题。强烈推荐:)

        12
  •  1
  •   bendewey    16 年前

    从锁中调用公共类会导致死锁

    public class ThreadedClass
    {
        private object syncHandle = new object();
    
        public event EventHandler Updated = delegate { };
        public int state = 0;
    
        public void DoSmething()
        {
            lock(syncHandle)
            {
                // some locked code
                state = 1;
    
                Updated(this, EventArgs.Empty);
            }
        }
    
        public int State { 
            get
            {
                int returnVal;
                lock(syncHandle)
                    returnVal = state;
                return returnVal;            
            }
        }
    }
    

    public void DoSmething()
    {
        lock(syncHandle)
        {
            // some locked code
            state = 1;
        }
        // this should be outside the lock
        Updated(this, EventArgs.Empty);
    }
    
        13
  •  1
  •   Bill Michell    16 年前

    双重检查锁定是否正确 broken

        14
  •  1
  •   olli-MSFT    16 年前

    一些经验法则:

    • 写入类属性 ( 静止的
    • 必须同步写入实例属性
    • 尽可能保持所有变量的局部性(不要将它们放在成员中) 上下文(除非有意义)
    • 标记只读不可变的变量

    (2) 锁定对可变类或实例属性的访问:属于同一类或实例属性的变量 invariant

    (3) 避免 Double Checked Locking

    (4) 当你跑步时要保持锁定 分布式 操作(调用子程序)。

    (5) 避免 busy waiting

    (7) 在同步块中时不允许获取客户端控件。

    (8) 评论!这确实有助于理解另一个人在声明这个部分是同步的还是那个变量是不可变的时的想法。

        15
  •  1
  •   user41871 user41871    16 年前

    不正确的封装会导致争用和死锁 . 这种情况可能有很多种不同的发生方式,尽管我看到了两种特别的情况:

    1. 给变量不必要的大范围。 例如,有时人们会在实例范围内声明一个变量,而本地范围会这样做。这可以为不需要存在的种族创造潜力。

    2. 不必要地暴露锁。

    这里有一个简单的例子 以上#1示例

    public class CourseService {
        private CourseDao courseDao;
        private List courses;
    
        public List getCourses() {
            this.courses = courseDao.getCourses();
            return this.courses;
        }
    }
    

    在这个例子中,不需要 courses 变量具有实例作用域,现在并发调用 getCourses() 我可以参加比赛。

        16
  •  1
  •   Rubens Farias    15 年前
        17
  •  0
  •   Ben Hoffstein    16 年前

    deadlocks (两个相互竞争的进程被卡住,等待对方释放一些资源)和 race conditions (当事件的时间和/或依赖性可能导致意外行为时)。这是一个值得一提的问题 video about "Multithreading Gotchas" 也。

        18
  •  0
  •   Kb.    16 年前

    您还可以研究进程外类型的并发问题


        19
  •  0
  •   Matt K    16 年前

    并发问题非常难以调试。作为一种预防措施,可以完全禁止访问共享对象而不使用互斥体,这样程序员就可以很容易地遵循规则。我在操作系统提供的互斥量和信号量等周围制作包装器来实现这一点。

    以下是我过去的一些令人困惑的例子:

    我曾经为Windows开发打印机驱动程序。为了防止多个线程同时写入打印机,我们的端口监视器使用如下结构: BOOL OpenPort(){GrabCriticalSection();} BOOL ClosePort(){ReleaseCriticalSection();} BOOL WritePort(){writestuff();}

    我也曾经做过打印机固件的工作。本例中的打印机使用了一个名为uCOS(发音为“mucus”)的RTOS,因此每个函数都有自己的任务(打印头电机、串行端口、并行端口、网络堆栈等)。此打印机的一个版本具有插入打印机主板上串行端口的内部选项。在某个时刻,人们发现打印机会从这个外设上读取两次相同的结果,之后的每一个值都会出现顺序错误(e、 外设读取一个序列1,7,3,56,9230,但是我们会看到1,7,3,3,56,9230。此值被报告给计算机并放入数据库,因此有一堆ID号错误的文档是非常糟糕的)根本原因是未能遵守保护设备读取缓冲区的互斥锁(因此,我在本回复开头给出了建议)

        20
  •  0
  •   Steve    16 年前

    这不是陷阱,更多的是基于他人反应的提示。NET framework的readerwriterlockslim在许多情况下都会比“lock”语句大大提高性能,同时又是可重入的。

        21
  •  0
  •   Nick Gunn    16 年前

    可组合性。在任何一个非平凡的系统中,不同子系统之间的同步的特殊方法使得它们之间的交互通常容易出错,有时甚至是不可能的。看到了吗 this video 例如,即使是最简单的代码也容易出现这些问题。

    Actor model of concurrent computation (异步变体)。