![]() |
1
19
在这个问题上已经有很多很好的答案和指针了,但是让我补充一点。 不要依赖测试来发现竞争条件和死锁 假设您已经准备好了所有良好的开发过程:每个组件的单元测试、每个夜间构建的冒烟测试、要求每个开发人员的更改在签入前通过测试等等。 所有这些都是好的,但是它导致了一种“好吧,它通过了测试套件,所以它不可能是我代码中的bug”的态度,这在并发编程中并不能很好地为您服务。实时并发错误很难重现。在一段代码失败之前,你可以用一个竞态条件运行它10亿次。 您将不得不调整您的流程,以便更加重视由您的最佳头脑进行的代码评审。仅针对并发性问题进行单独的代码检查并不是一个坏主意。 您必须更加强调使应用程序能够自我调试。也就是说,当您在测试实验室或客户站点遇到故障时,您需要确保捕获并记录了足够的信息,以便进行最终的验尸,因为您能够在方便的时候复制错误报告的可能性可以忽略不计。 您必须更加强调代码中的偏执健全性检查,以便在尽可能接近问题的地方检测到错误,而不是在50000行代码之外。 多疑。非常偏执。 |
![]() |
2
16
一个是 race condition ,基本上是假设一段代码将在另一段并发代码之前/之后运行。 还有 deadlocks |
![]() |
3
13
我教我的朋友和同事很多并发性。以下是一些大的陷阱:
我还看到:
|
![]() |
4
12
最大的陷阱之一是首先使用并发。并发性增加了大量的设计/调试开销,因此您必须检查问题,看看它是否真的需要并发性。 在某些领域,并发当然是不可避免的,但是当它是可以避免的时候,就要避免它。 |
![]() |
5
11
然而,同步对共享数据的访问是很棘手的。 以下是任何编写共享数据同步代码的人都应该能够回答的一些问题:
|
![]() |
6
4
要记住的一个事实是,即使最初的开发人员让他们的任务分配模型正常工作(这是一个很大的if),那么接下来的维护团队肯定会以难以想象的方式把事情搞砸。这样做的好处是限制整个系统中并发的痕迹。尽你最大的努力确保你的系统大部分都没有意识到并发正在发生。这就减少了不熟悉任务分配模式的人不经意间把事情搞砸的机会。 人们常常对线程/任务发狂。每件事都有自己的思路。最终的结果是,几乎每一段代码都必须密切注意线程问题。它迫使原本简单的代码充斥着锁定和同步混淆。每次我看到这一点,系统最终都会变成一个无法维护的烂摊子。然而,每次我看到这一点,最初的开发人员仍然坚持认为这是一个很好的设计:( 就像多重继承一样,如果你想创建一个新的线程/任务,那么在证明你错了之前就假设你错了。我甚至无法计算我看到模式线程A调用线程B,然后线程B调用线程C,然后线程C调用D的次数,所有这些都在等待前一个线程的响应。所有的代码都是通过不同的线程进行冗长的函数调用。当函数调用工作正常时不要使用线程。
我发现创建一个能够处理几乎所有并发问题的核心基础设施效果最好。如果核心基础设施之外有任何线程必须与另一个软件进行通信,那么它们必须通过核心基础设施。这样系统的其余部分就可以保持不知道并发性,并发性问题可以由希望理解并发性的人来处理。 |
![]() |
7
3
如其他答案所述,两个最可能的问题是 和 竟态条件 . 不过,我的主要建议是,如果您希望培训一个关于并发主题的团队,我强烈建议您 你自己训练一下 . 找一本关于这个主题的好书,不要依赖网站上的几段话。一本好书取决于您所使用的语言:Brian Goetz的《Java并发实践》适合于该语言,但是还有很多其他的书。 |
![]() |
8
3
一切都归结为共享数据/共享状态。如果您不共享数据或状态,那么就没有并发问题。 大多数人,当他们想到并发时,会想到单个进程中的多线程。 考虑这一点的一种方法是,如果将流程拆分为多个流程,会发生什么情况。他们必须在哪里相互交流?如果你能清楚地知道这些进程在哪里必须相互通信,那么你就对它们共享的数据有了一个很好的了解。
(剩下的部分不适用于Java线程,我不使用它,因此对它知之甚少)。
如果没有一个在实际系统中非常罕见的小心级别,您不可能使多个锁正常工作。
|
![]() |
9
2
根据我的经验,许多(熟练的)开发人员缺乏并发理论的基础知识。Tanenbaum或Stallings的经典操作系统教科书很好地解释了并发的理论和含义:互斥、同步、死锁和饥饿。良好的理论背景是成功使用并发的必要条件。 也就是说,在不同的编程语言和不同的库之间,并发支持有很大的不同。此外,测试驱动开发在检测和解决并发问题方面并没有取得太大进展(尽管短暂的测试失败表明存在并发问题)。 |
![]() |
10
2
我所看到的第一个陷阱是太多的数据共享。 我认为处理并发性的更好方法之一是多进程而不是线程。这样,线程/进程之间的通信严格限制在所选的管道、消息队列或其他通信方法中。 |
![]() |
11
1
下面是一个关于并发的很好的资源,特别是在Java中: http://tech.puredanger.com/ alexmiller列出了处理并发时可能遇到的许多不同问题。强烈推荐:) |
![]() |
12
1
从锁中调用公共类会导致死锁
|
![]() |
13
1
双重检查锁定是否正确 broken |
![]() |
14
1
一些经验法则:
(2) 锁定对可变类或实例属性的访问:属于同一类或实例属性的变量 invariant (3) 避免 Double Checked Locking (4) 当你跑步时要保持锁定 分布式 操作(调用子程序)。 (5) 避免 busy waiting
(7) 在同步块中时不允许获取客户端控件。 (8) 评论!这确实有助于理解另一个人在声明这个部分是同步的还是那个变量是不可变的时的想法。 |
![]() |
15
1
不正确的封装会导致争用和死锁 . 这种情况可能有很多种不同的发生方式,尽管我看到了两种特别的情况:
这里有一个简单的例子 以上#1示例 public class CourseService { private CourseDao courseDao; private List courses; public List getCourses() { this.courses = courseDao.getCourses(); return this.courses; } }
在这个例子中,不需要
|
![]() |
16
1
刚找到这张纸,听起来很有趣: A Study of Common Pitfalls in Simple Multi-Threaded Programs |
![]() |
17
0
deadlocks (两个相互竞争的进程被卡住,等待对方释放一些资源)和 race conditions (当事件的时间和/或依赖性可能导致意外行为时)。这是一个值得一提的问题 video about "Multithreading Gotchas" 也。 |
![]() |
18
0
您还可以研究进程外类型的并发问题
|
![]() |
19
0
并发问题非常难以调试。作为一种预防措施,可以完全禁止访问共享对象而不使用互斥体,这样程序员就可以很容易地遵循规则。我在操作系统提供的互斥量和信号量等周围制作包装器来实现这一点。 以下是我过去的一些令人困惑的例子: 我曾经为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
这不是陷阱,更多的是基于他人反应的提示。NET framework的readerwriterlockslim在许多情况下都会比“lock”语句大大提高性能,同时又是可重入的。 |
![]() |
21
0
可组合性。在任何一个非平凡的系统中,不同子系统之间的同步的特殊方法使得它们之间的交互通常容易出错,有时甚至是不可能的。看到了吗 this video 例如,即使是最简单的代码也容易出现这些问题。 |
![]() |
user107586 · 如何处理等待句柄不会导致无限循环? 8 月前 |
![]() |
ron burgundy · 获取-释放语义是否跨线程传递?[副本] 8 月前 |
![]() |
BenjiFB · C#内存缓存:在一次操作中追加到列表? 8 月前 |
![]() |
András Takács · Python多线程问题 1 年前 |
|
András Takács · Python多线程错误 1 年前 |