Jon*_*eet 67
(假设.NET;类似的东西适用于其他平台.)
嗯,有很多事情需要考虑.我建议:
Monitor.Wait应该几乎总是成为条件循环的一部分,等待条件变为真,如果不是则再次等待.这是我的头脑 - 我可能会想到更多,如果这对你有用,但我会停在那里,以防它不是.
Dan*_*ker 35
学习正确编写多线程程序非常困难且耗时.
因此,第一步是:将实现替换为根本不使用多个线程的实现.
然后,当你发现一些非常简单的安全方法的时候,当你发现真正的需要时,小心地将线程重新插入.可靠地工作的非线程实现远比破坏的线程实现好.
当您准备好开始时,支持使用线程安全队列在线程之间传输工作项的设计,并注意确保这些工作项一次只能由一个线程访问.
尽量避免lock在代码周围喷涂块,希望它能成为线程安全的.它不起作用.最终,两个代码路径将以不同的顺序获取相同的锁,并且所有内容都将停止(每两周一次,在客户的服务器上).如果您将线程与触发事件组合在一起,并且在触发事件时保持锁定,则这种情况尤其可能 - 处理程序可能会取出另一个锁定,现在您按特定顺序拥有一对锁定.如果在其他情况下它们以相反的顺序被取出怎么办?
简而言之,这是一个如此庞大而困难的主题,我认为在一个简短的回答中提出一些指示并说"你走了!"可能会产生误导. - 我确信这里并没有很多有学问的人给出答案的意图,但这是许多人从总结建议中得到的印象.
相反,买这本书.
这是一个来自这个网站的措辞非常好的摘要:
多线程也有缺点.最大的问题是它可以导致更复杂的程序.拥有多个线程本身并不会产生复杂性; 它是创建复杂性的线程之间的交互.这适用于相互作用是否是有意的,并且可能导致长的开发周期,以及对间歇性和不可再现的错误的持续敏感性.出于这个原因,在多线程设计中保持这种交互很简单 - 或者根本不使用多线程 - 除非你有一种特殊的重写和调试倾向!
通过让一堆线程在单个地址空间中松散然后使用锁来尝试处理由此产生的数据争用和协调问题来处理并发性的传统方法在正确性和可理解性方面可能是最糟糕的.
Mat*_*vis 14
(就像Jon Skeet,其中大部分假设是.NET)
看似有争议的风险,像这样的评论只是困扰我:
学习正确编写多线程程序非常困难且耗时.
应尽可能避免线程......
如果不利用某些容量的线程,编写任何有意义的软件几乎是不可能的.如果您在Windows上,打开任务管理器,启用"线程计数"列,您可以一方面指望使用单个线程的进程数.是的,不应该仅仅为了使用线程而使用线程,也不应该使用线程,但坦率地说,我相信这些陈词滥调经常被使用.
如果我不得不为真正的新手煮多线程编程,我会说:
总而言之,我会说线程并不困难,但它可能很乏味.仍然,正确的线程应用程序将更具响应性,您的用户将非常感激.
编辑:在C#中,ThreadPool.QueueUserWorkItem(),异步委托,各种BeginXXX/EndXXX方法对等都没有"极其困难".如果有的话,这些技术可以更容易地以线程方式完成各种任务.如果您有一个GUI应用程序可以执行任何繁重的数据库,套接字或I/O交互,则实际上不可能在不利用幕后线程的情况下使前端响应用户.我上面提到的技术使这成为可能,并且使用起来轻而易举.当然,了解陷阱是很重要的.我只是相信我们做程序员,特别是年轻人,当我们谈论"非常困难"的多线程编程是什么时,或者应该如何"避免"线程时,这是一种损害.像这样的评论过于简化问题并夸大了神话,因为事实是线程从未如此简单.有正当理由使用线程,这样的陈词滥调对我来说似乎适得其反.
您可能对CSP或其他用于处理并发的理论代数之一感兴趣.大多数语言都有CSP库,但如果语言不是为它设计的,那么正确使用它需要一些规范.但最终,每种并发/线程都归结为一些相当简单的基础:避免共享可变数据,并准确理解每个线程在等待另一个线程时可能必须阻塞的时间和原因.(在CSP中,共享数据根本不存在.每个线程(或CSP术语中的进程)只允许通过阻塞消息传递通道与其他人通信.由于没有共享数据,竞争条件就会消失.阻塞,很容易推理同步,并逐字地证明不会发生死锁.)
另一个更容易改装到现有代码中的好习惯是为系统中的每个锁分配优先级或级别,并确保始终遵循以下规则:
遵循这些规则意味着发生死锁根本不可能.然后你只需要担心可变共享数据.
重点强调 Jon 发布的第一点。您拥有的不可变状态越多(即:const 的全局变量等),您的生活就会越轻松(即:您需要处理的锁越少,推理就越少做关于交错顺序等...)
此外,通常情况下,如果您有需要多个线程访问的小对象,有时最好在线程之间复制它,而不是拥有一个共享的、可变的全局变量,您必须持有一个锁才能读取/变异。这是您的理智和内存效率之间的权衡。