![]() |
1
107
学习穿线的热情是很好的,别误会我。热衷于 使用大量线程 相反,它是我所说的“线幸福病”的症状。 刚刚了解线程功能的开发人员开始问“我可以在一个程序中创建多少线程?”这很像一个英语专业的学生问“我能在一个句子中用多少个词?”对于作家来说,典型的建议是保持句子简短,切中要害,而不是把尽可能多的单词和想法塞进一个句子中。线程是相同的方式;正确的问题不是“我可以从创建中获得多少?”而是“我如何编写这个程序,使线程的数量 最低限度 有必要完成工作吗?” 线程解决了很多问题,这是事实,但它们也带来了巨大的问题:
你想要的是螺纹像州际公路:没有交通灯,高度平行,交叉在少数非常明确,精心设计的点。 这很难做到。 大多数重多线程程序更像是密集的城市核心,到处都有红绿灯。
具有自定义线程管理的多线程程序需要 全球的 理解 一切 一根线会这样做的 可能地 影响从其他线程可见的数据。你必须把整个计划都放在脑子里,并且理解 全部的 两个线程相互作用的可能方式,以使其正确并防止死锁或数据损坏。这是一个巨大的成本支付,并极易出现错误。
代码正确吗?如果它是单线程的,可能。如果它是多线程的,是什么阻止另一个线程删除最后剩余的项目? 之后 是否执行对IsEmpty的调用?没什么,就是这样。这段代码在本地看起来很好,在多线程程序中是一颗等待爆炸的炸弹。基本上,代码实际上是:
这显然是相当无用的。 所以假设您决定通过锁定队列来解决这个问题。这是对的吗?
当然,这也不对。假设执行导致代码运行,该代码在当前被另一个线程锁定的资源上等待,但该线程在等待队列锁定-会发生什么?两个线程都将永远等待。在一大块代码周围加锁需要知道 一切 代码可以 可能地 做 任何 共享资源,这样您就可以计算出是否存在死锁。同样,这对于编写应该是非常简单的代码的人来说是一个非常沉重的负担。(这里要做的正确的事情可能是提取锁中的工作项,然后在锁外执行它。但是…what if the items are in a queue because they have to be executed in a particular order? 现在代码也错了,因为其他线程可以先执行后面的作业。)
多线程程序可以 不 做出保证。如果您在运行此线程时在另一线程上检查b和x,则 可以 如果执行了优化,请参阅b change before x is accessed。 在单线程程序中,读和写可以在逻辑上按时间前后移动,在多线程程序中可以观察到这些移动。 这意味着,为了编写多线程程序,如果逻辑中有依赖于所观察到的事情发生的顺序与实际编写代码的顺序相同,则必须 详细的 理解语言和运行时的“内存模型”。您必须准确地知道对于访问如何能够及时移动做出了哪些保证。而且,你不能简单地在你的x86设备上测试,并希望得到最好的结果;与其他一些芯片相比,x86芯片有相当保守的优化。 这只是对编写自己的多线程逻辑时遇到的几个问题的简要概述。There are plenty more. 所以,一些建议:
|
![]() |
2
11
线程的显式管理不是 本质上 这是件坏事,但它充满危险,除非绝对必要,否则不应该做。 说螺纹绝对是件好事,就像说螺旋桨绝对是件好事:螺旋桨在飞机上工作得很好(当喷气发动机不是更好的选择时),但在汽车上却不是个好主意。 |
![]() |
3
8
除非您调试了三向死锁,否则您无法理解线程处理会导致什么样的问题。或者花了一个月的时间寻找一个每天只发生一次的比赛条件。所以,继续向前,双脚都跳进去,犯下你需要犯的所有错误,学会害怕野兽,学会怎样做才能避免麻烦。 |
![]() |
4
6
除非您能够编写一个成熟的内核调度程序,否则您将得到显式的线程管理。 总是 错了。 线程可能是自热巧克力以来最棒的东西,但是并行编程非常复杂。然而,如果你设计的线程是独立的,那么你就不能射自己的脚。 根据经验法则,如果一个问题被分解成线程,那么它们应该尽可能独立,尽可能少但定义良好的共享资源,具有最简单的管理概念。 |
![]() |
5
6
我无法提供比这里更好的答案。但我 可以 offer a concrete example of some multithreaded code 在我的工作中 那是灾难性的。 我的一个同事,和你一样,在他第一次了解这些线的时候,对线非常热情。所以在整个程序中开始有这样的代码:
基本上,他是创造线程各地。 所以最终 另一个 同事发现了这一点,并告诉开发负责人: 不要那样做! 创建线程是很昂贵的,您应该使用线程池等。因此,代码中最初看起来像上述代码片段的许多地方开始重写为:
大的进步,对吗?一切又恢复正常了?
好吧,只是有个特别的电话
最终的结果是线程池实际上充满了长阻塞调用,导致了其他代码 想象上的 很快就排好队,直到明显比原来晚了才开始运行。 当然,这个故事的寓意并不是说创建自己的线程的第一个方法是正确的(它不是)。实际上,使用线程很困难,而且容易出错,正如其他人所说,您应该 非常 使用时要小心。 在我们的特殊情况下,我们犯了许多错误:
我很高兴这么说 今天 我们仍然活着,我们的代码比以前更健康。而我们 做 在许多我们认为合适的地方使用多线程,并测量了性能提升(例如,在接收市场数据标记和交易所确认传出报价之间减少延迟)。但我们从中吸取了一些非常重要的教训。如果你曾经在一个大的、高度多线程的系统上工作过,那么很可能你也会这样做。 |
![]() |
6
4
我认为第一句话最好这样解释:用
many advanced APIs now available
,几乎没有必要手动编写自己的线程代码。新的API是
许多
易于使用,以及
许多
更难搞砸!.然而,使用老式的线程,您必须非常好地
不
搞砸。The old-style APIs (
第二个声明是从设计的角度来看的,IMO。在一个关注点完全分离的设计中,后台任务不应该直接接触到UI来报告更新。应该有一些分离,使用MVVM或MVC这样的模式。 |
![]() |
7
3
我首先质疑这种看法:
不要让我弄错了“线程是一种非常通用的工具”,但这种热情似乎很奇怪。特别是,它表明您可能在很多情况下使用线程,而这些情况下线程根本没有意义(但我可能会再次误解您的热情)。 正如其他人所指出的,线程处理是另外非常复杂和复杂的。螺纹包装机 存在 只有在极少数情况下,它们才必须得到明确的处理。对于大多数应用程序,可以隐含线程。
例如,如果您只想在保持GUI响应的同时将计算推送到后台,那么一个更好的解决方案通常是使用回调(这使得计算似乎是在后台完成的,而实际上是在同一线程上执行),或者使用方便的包装器,如
最后一点,创建线程实际上非常昂贵。使用线程池可以降低这一成本,因为在这里,运行时会创建许多线程,这些线程随后会被重用。当人们说线程的显式管理不好时,这就是他们所指的全部内容。 |
![]() |
8
2
许多高级GUI应用程序通常由两个线程组成,一个用于UI,一个(有时更多)用于处理数据(复制文件、进行大量计算、从数据库加载数据等)。
处理线程不应该直接更新用户界面,用户界面对它们应该是一个黑框(选中维基百科
包封
)
如果您从处理线程更新UI,您将无法重用代码,如果您想更改部分代码,则会遇到更大的问题。 |
![]() |
9
2
我想你 应该 尽可能多地体验线程,并了解使用线程的好处和陷阱。只有通过实验和使用,你对它们的理解才会增长。尽可能多地阅读这个主题。 当涉及到C和用户界面时(它是单线程的,并且您只能修改在UI线程上执行的代码上的用户界面元素)。我使用下面的实用程序来保持自己的神智和晚上睡得很好。
您可以在任何需要更改ui元素的线程中使用它,例如:
|
![]() |
10
1
我觉得穿线是件好事。但是,与他们合作是非常困难的,需要大量的知识和培训。主要问题是,当我们想要从其他两个线程访问共享资源时,这可能会导致不希望的效果。 考虑经典的例子:您有两个线程,它们从共享列表中获取一些项目,并且在执行某些操作之后,它们会从列表中删除这些项目。 定期调用的线程方法可能如下所示:
记住,理论上,线程可以在不同步的代码的任何行上切换。因此,如果列表只包含一个项,则一个线程可以通过
所以,这就是为什么不同步的UI必须始终在单个线程中运行,而其他线程不应干扰UI的原因。
在.NET的早期版本中,如果要在其他线程中更新UI,则必须使用
|
![]() |
11
1
尽量保持ui线程和处理线程独立的一个重要原因是,如果ui线程冻结,用户会注意到并且不高兴。让UI线程快速燃烧是很重要的。如果您开始将UI内容移出UI线程或将处理内容移到UI线程,则应用程序失去响应的风险更高。 另外,很多框架代码都是刻意编写的,希望您能将UI和处理分开;当您将两者分开时,程序会工作得更好,而当您不分开时,程序会遇到错误和问题。我不记得我在这方面遇到的任何具体问题,尽管我有模糊的回忆在过去,试图设置用户界面在用户界面之外负责的某些属性,并且让代码拒绝工作;我不记得它是没有编译,还是抛出了异常。 |
![]() |
12
0
在从非UI线程更新UI时,需要注意以下几点:
在许多情况下,我的首选样式是将每个控件的状态封装在一个不可变的类中,然后具有一个标志,该标志指示是否不需要、挂起或需要但不挂起更新(如果在完全创建控件之前请求更新该控件,则可能发生后一种情况)。如果需要更新,控件的更新例程应该从清除更新标志、获取状态和绘制控件开始。如果设置了更新标志,它应该重新循环。若要请求另一个线程,例程应使用interlocked.exchange将更新标志设置为“更新挂起”,如果它不是挂起的,则尝试开始更新例程;如果BeginInvoke失败,则将更新标志设置为“需要但不是挂起”。 如果在控件的更新例程检查并清除其更新标志后尝试控制,则很可能第一次更新将反映新值,但更新标志仍将被设置,从而强制重新绘制一个额外的屏幕。在这种情况下,它将相对无害。重要的是,控件最终将以正确的状态绘制,而不会有多个BeginInvoke挂起。 |
![]() |
A B · C#Excel自动调整列避免长文本时出错 6 月前 |
![]() |
Megrez7 · C#ToArray转换合并为一行,导致数组元素更改 6 月前 |
![]() |
Aycon · 在工厂方法中释放部分创建的对象的正确方法是什么? 6 月前 |
|
Sei · Avalonia/WPF将路由器传递到控制模板 6 月前 |