Tom*_*ght 45 c# multithreading
在上一个问题中,我做了一个失礼.你看,我一直在阅读有关线程的文章,并且给人的印象是,自从奇异果jello以来,它们是最美味的东西.
想象一下,当我读到这样的东西时,我的困惑:
[T] hreads是一件非常糟糕的事情.或者,至少,线程的显式管理是一件坏事
和
跨线程更新UI通常表明您正在滥用线程.
因为每当有什么东西让我感到困惑时我就会杀了一只小狗,考虑一下这个机会让你的业力重新回到黑色......
我该如何使用线程?
Eri*_*ert 109
学习线程的Enthusiam很棒; 别误会我的意思.相比之下,使用大量线程的热情是我称之为线程幸福病的症状.
刚刚了解了线程强大功能的开发人员开始提出诸如"我可以在一个程序中创建多少个线程?"之类的问题.这就像一位英语专业的学生,"我可以在句子中使用多少个单词?" 对作者的典型建议是保持你的句子简短而重要,而不是试图将尽可能多的单词和想法塞进一个句子中.线程是一样的; 正确的问题不是"创造多少可以逃脱?" 而是"如何编写这个程序,以便线程数是完成工作所需的最小数量?"
线程解决了很多问题,这是事实,但它们也带来了巨大的问题:
你想要的是线程像州际高速公路一样:没有交通信号灯,高度平行,交叉在少数非常明确,精心设计的点上. 这很难做到. 大多数多线程程序更像是密集的城市核心,到处都有红绿灯.
具有自定义线程管理的多线程程序需要全局了解线程将要执行的所有操作,这些操作可能会影响从另一个线程可见的数据.您几乎必须掌握整个程序,并了解两个线程可以进行交互的所有可能方式,以使其正确并防止死锁或数据损坏.这是一个很大的支付成本,并且非常容易出错.
从本质上讲,线程使你的方法撒谎.让我给你举个例子.假设你有:
if(!queue.IsEmpty)queue.RemoveWorkItem().Execute();
这段代码是否正确?如果它是单线程的,可能.如果它是多线程的,那么在执行对IsEmpty的调用之后阻止另一个线程移除最后剩余项的是什么?没什么,那是什么.这个代码在本地看起来很好,是一个等待在多线程程序中运行的炸弹.基本上,代码实际上是:
if (queue.WasNotEmptyAtSomePointInThePast) ...
Run Code Online (Sandbox Code Playgroud)
这显然是没用的.
因此,假设您决定通过锁定队列来解决问题.这是正确的吗?
lock(queue) {if (!queue.IsEmpty) queue.RemoveWorkItem().Execute(); }
Run Code Online (Sandbox Code Playgroud)
That's not right either, necessarily. Suppose the execution causes code to run which waits on a resource currently locked by another thread, but that thread is waiting on the lock for queue - what happens? Both threads wait forever. Putting a lock around a hunk of code requires you to know everything that code could possibly do with any shared resource, so that you can work out whether there will be any deadlocks. Again, that is an extremely heavy burden to put on someone writing what ought to be very simple code. (The right thing to do here is probably to extract the work item in the lock and then execute it outside the lock. But... what if the items are in a queue because they have to be executed in a particular order? Now that code is wrong too because other threads can then execute later jobs first.)
Multi-threaded programs do not make that guarantee. If you are examining b and x on a different thread while this one is running then you can see b change before x is accessed, if that optimization is performed. Reads and writes can logically be moved forwards and backwards in time with respect to each other in single threaded programs, and those moves can be observed in multi-threaded programs.
This means that in order to write multi-threaded programs where there is a dependency in the logic on things being observed to happen in the same order as the code is actually written, you have to have a detailed understanding of the "memory model" of the language and the runtime. You have to know precisely what guarantees are made about how accesses can move around in time. And you cannot simply test on your x86 box and hope for the best; the x86 chips have pretty conservative optimizations compared to some other chips out there.
That's just a brief overview of just a few of the problems you run into when writing your own multithreaded logic. There are plenty more. So, some advice:
Mus*_*sis 11
线程的显式管理本质上并不是一件坏事,但它会带来危险,除非绝对必要,否则不应该做.
说线程绝对是一件好事,就像说螺旋桨绝对是一件好事:螺旋桨在飞机上工作得很好(当喷气发动机不是更好的选择),但对于汽车来说不是一个好主意.
除非你调试了三方死锁,否则你无法理解线程可能导致什么样的问题.或者花了一个月的时间来追逐每天只发生一次的竞赛条件.所以,继续双脚跳进去,做出所有的错误,你需要学会害怕野兽以及如何避免麻烦.
除非您处于能够编写完全成熟的内核调度程序的水平,否则您将得到显式线程管理总是错误的.
自热巧克力以来,线程可能是最棒的东西,但并行编程非常复杂.但是,如果你设计你的线程是独立的,那么你就不能用脚射击自己.
作为大拇指的先行规则,如果问题被分解为线程,它们应尽可能独立,尽可能少但定义明确的共享资源,具有最简约的管理概念.
我无法提供比已经存在的更好的答案.但我可以提供一些具体的例子,说明我们在工作中实际遇到的一些多线程代码是灾难性的.
我的一个同事,像你一样,在第一次了解它们时非常热衷于线程.所以在整个程序中开始有这样的代码:
Thread t = new Thread(LongRunningMethod);
t.Start(GetThreadParameters());
Run Code Online (Sandbox Code Playgroud)
基本上,他正在创造各地的线程.
所以最终另一位同事发现了这一点并告诉负责的开发商:不要这样做!创建线程是昂贵的,你应该使用线程池等等.所以代码中最初看起来像上面代码片段的很多地方开始被重写为:
ThreadPool.QueueUserWorkItem(LongRunningMethod, GetThreadParameters());
Run Code Online (Sandbox Code Playgroud)
大改进吧?一切都恢复了理智?
好吧,除了有一个特别的呼吁LongRunningMethod,可能会阻止 - 很长一段时间.突然间,我们偶尔会看到它发生了我们的软件应该立即做出反应的事情......它只是没有.事实上,它可能没有反应几秒钟(澄清:我为一家贸易公司工作,所以这是一场彻底的灾难).
最终发生的事情是线程池实际上填满了长时间阻塞的调用,导致其他代码应该很快就会排队并且直到明显晚于它应该具有的速度才会运行.
当然,这个故事的寓意并不是创建自己的线程的第一种方法是正确的做法(事实并非如此).这真的只是使用线程很难并且容易出错,并且正如其他人已经说过的那样,使用它们时应该非常小心.
在我们的特殊情况下,犯了很多错误:
lock关键字使用的结果.我很高兴地说,今天,我们还活着,我们的代码处于比以前更健康的状态.我们确实在很多地方使用多线程,我们认为它是合适的并且已经测量了性能提升(例如减少了接收市场数据滴答和交换确认的传出报价之间的延迟).但是我们很难学到一些非常重要的课程.如果你曾经在一个大型的高度多线程系统上工作,你也会有机会.