有人能给我解释一下线程吗?

12

我一直在考虑在我的应用程序中添加线程处理以加快执行速度,但问题是我实在不知道如何使用线程,或者什么被认为是“线程安全”的。例如,游戏引擎如何在其渲染过程中利用线程,或者在什么情况下线程只会被视为阻碍?有人可以指引我一些资源来帮助我学习更多,或者在这里解释一下吗?


2
这是非常普遍的。一个线程只是执行命令的工作程序。 "线程安全" 是确保两个或更多线程在交互时不会引起问题。至于游戏引擎利用线程...有大约一百万种方法可以做到这一点。 - Travis Gockel
3
坦率地说,不要考虑添加线程。这就像说你具有基本的急救知识,想要做脑部手术一样。回去学习基础知识。 - Martin York
这个问题太宽泛了,无法以任何合理的方式回答。 - Kev
10个回答

32

这是一个非常广泛的话题。但如果我对线程一无所知,以下是我想了解的内容:

  • 它们是单个进程内的执行单位,发生“并行” - 这意味着处理器中当前执行单位会快速切换。可以通过不同的方式实现。切换被称为“上下文切换”,与此相关的有一些开销。

  • 它们可以共享内存!这就是问题可能发生的地方。我将在稍后的要点中更深入地讨论此事。

  • 并行化应用程序的好处在于使用计算机的不同部分的逻辑可以同时发生。也就是说,如果您的进程一部分是I/O绑定,另一部分是CPU绑定,那么I/O密集操作不必等待CPU密集操作完成。某些语言还允许您在具有多核处理器的情况下同时运行线程(从而并行化CPU密集型操作),尽管这并不总是成立。

  • 线程安全意味着没有竞争条件,这是一个术语,用于描述当您的进程的执行取决于时间(您不希望依赖于此)时发生的问题。例如,如果您有线程AB都在增加一个共享计数器C,则可能会出现这种情况:A读取C的值,然后B读取C的值,然后A使用C+1覆盖C,然后B再次使用C+1覆盖C。请注意,C只实际增加了一次!

  • 避免竞争条件的一些常见方法包括同步,它排除对共享状态的相互访问,或者根本不具有任何共享状态。但这只是冰山一角 - 线程安全是一个非常广泛的话题。

我希望这能有所帮助!请理解,这只是一个需要很多学习的东西的快速介绍。我建议您找到一些关于您偏爱的编程语言中多线程的资源,并进行深入阅读。


那帮了我很多!谢谢! - Orm
+1 很好的主题介绍。 - Grundlefleck
这实际上给了我一个线程渲染的想法。将存储的游戏对象的容器分成两半,然后同时使用两个线程渲染第一半或第二半。或者将容器分成四分之一或八分之一。如果我错了,请立即指出。 - Orm
@Orm:我对图形渲染也不是很熟悉,但需要记住的一件好事是(在多核环境之外),并行化任务所带来的加速只有在同时运行不同类型的任务时才会发生,其中类型取决于它使用的机器部分(请参见我的第三个要点)。例如,并行化像归并排序这样的东西将不会产生任何好处,因为它全部都在CPU中完成,而GUI应用程序必须被线程化,因为您不希望I/O线程让用户与应用程序交互时等待CPU密集型任务。 - danben
好的,那么您的意思是我应该研究线程编程,例如读取用户输入并将其与图形处理分开处理? - Orm
显示剩余5条评论

2

关于线程,有四个需要知道的事情。

  1. 线程类似于进程,但它们共享内存。

  2. 线程通常具有硬件、操作系统和语言支持,这可能使它们比进程更好。

  3. 线程需要支持许多琐碎的小事情(如锁和信号量),以便不会将它们共享的内存置于不一致状态。这使得它们有点难以使用。

  4. 锁定不是自动的(在我所知道的语言中),因此您必须非常小心地处理它们(隐式)共享的内存。


1

线程不能加速应用程序。算法可以加速应用程序。如果适当,线程可以在算法中使用。


2
线程可以在某些情况下加速应用程序。比如,当串行阻塞任务可以拆分成并行任务时。这种效果在多核系统上更为明显,而多核系统已经成为常态。考虑一个必须发出TCP/IP请求并等待响应的任务。如果此任务在一个线程上阻塞,那么10个相同任务需要10倍的时间。如果并行处理,则可以将时间减少到仅略多于1倍。 - Grundlefleck
这是术语的更正吗? :) - Grundlefleck
是的,我已经学过算法课程并一直在学习算法,但我想通过多核处理器成为常态来拓宽自己的视野。 - Orm
1
对于那些持反对意见的人:我坚信只有在有意义的时候才应该将线程引入算法中,而不是过早地引入。 - Richard Pennington

1

好的,也许有人能更好地回答这个问题,但是线程的目的是进行后台处理,而不会冻结用户界面。您不想停止接受键盘输入或鼠标输入,并告诉用户:“稍等片刻,我想完成这个计算,只需要几秒钟。”(商业程序经常这样做还是令人惊讶的。

就线程安全而言,它意味着一个函数没有一些内部保存的状态。如果有的话,您无法同时使用多个线程。

至于线程编程,你只需要开始做,然后你就会遇到各种独特的线程编程问题,例如对数据的同步访问,在这种情况下,你必须决定使用一些同步方法,如临界区或互斥体或其他一些方法,每一种方法的行为略有不同。

至于进程和线程之间的差异(您没有提问),进程是操作系统级别的实体,而线程与程序相关联。在某些情况下,您的程序可能希望创建一个进程而不是一个线程。


2
尽管任何无状态的内容都是线程安全的,但线程安全并不意味着无状态。 - danben
抱歉,我现在有点挑剔... "...线程的目的是进行后台处理,而不会冻结用户界面。" - 这似乎表明这是线程的唯一目的,但这绝对不是真的,尽管这是其中之一的用途。 - Grundlefleck

1

线程只是同时执行多个任务的一种方式(假设运行它们的平台支持并行执行)。线程安全只是确保线程不会以有害的方式相互影响的简单方法(好吧,实际上与线程有关的事情没有什么是真正简单的)。

通常情况下,由于可能出现多种性能问题和复杂性问题,您不太可能看到系统使用多个线程来渲染屏幕上的图形。但是,与状态管理(或人工智能)相关的其他任务可以潜在地移动到单独的线程中。


1

线程编程的第一条规则:不要使用线程。第二条规则:如果你必须违反第一条规则……还是不要使用线程。第三条规则:好吧,你必须使用线程,那么在继续之前,先了解线程陷阱、锁定以及常见的线程问题,如死锁和活锁。

要明白,线程并不能加速任何事情,它只有在后台运行长时间进程时才有用,让用户可以在应用程序中做其他事情。如果你必须允许用户与应用程序交互,同时应用程序在后台执行其他任务,比如轮询套接字或等待来自应用程序其他地方的异步输入,那么你确实需要使用线程。

Effective JavaClean Code 中的线程部分都是线程及其陷阱的良好介绍。


4
普遍规律的第一条:不要概括。 - danben
5
当你有一个可并行的、CPU密集型的任务,并且有多个处理器(或核心)可用时,多线程确实能提供加速。将工作分成 n 个线程,完成工作所需的时间约为 1/n,其中 n 小于或等于处理器的数量。 - Wyzard
1
这是真的,而且随着比以往任何时候都更多的多核处理器的出现,这更加适用。理解了。但即使在多处理器环境中,仅仅为了优化而利用线程也是一个冒险的建议,只有在你的架构必须是异步的情况下才可行,例如编写某种会产生多个工作进程的服务器等。 - Dave Sims
我试图表达的观点(可能表述不当)是关于“不要使用线程”的,即线程会给您的代码库引入许多风险和不可预测性,如果可能的话,建议避免使用。仅仅为了“加速”应用程序而进行线程处理并不是一个好主意,特别是在面向对象的环境中,锁定、互斥等操作会创建一个越来越复杂和不可预测的环境。那里有龙藏着。 - Dave Sims
好吧,由于我的渲染引擎的初始设计本质上存在缺陷,所以在这次修订之后可能不需要线程。 - Orm

1

1

Orm,这是一个很好的问题。我认为所有认真的程序员都应该学习线程,因为最终你将会至少考虑使用它们,当发生这种情况时,你真的希望做好准备。并发错误可能非常微妙,避免它们的最佳方法是了解哪些惯用语是安全的(-ish)。

我强烈建议您花时间阅读Doug Lea的书《Java并发编程实战》

http://gee.cs.oswego.edu/dl/cpj/

Lea不仅花时间教授概念,还向您展示如何正确和不正确地使用并发编程原语(在Java中,但对于任何其他使用共享内存锁定/信号样式并发的环境也有帮助)。最重要的是,他教授了对并发编程难度的尊重。

我应该补充说,这种并发编程风格是最常见的,但不是唯一的方法。还有消息传递,它更安全,但需要您以不同的方式构建算法。


1

由于原帖非常广泛,并且标签中也有 C++,我认为以下指针是相关的:

Anthony Williams 是 Boost Thread 库的维护者,他正在编写一本名为“C++ Concurrency in Action”的书,您可以在此处找到其描述 here。第一章(介绍性章节)可免费获取PDF格式 here

此外,Herb Sutter(以他的“Exceptional C++”系列而著称)正在编写一本名为“Effective Concurrency”的书,其中许多文章都可以在草稿形式下获取 here


0

尽管这是一本很棒的书,但它假定读者已经掌握了Java并发机制的基础知识,而提问者可能还没有准备好。 - Grundlefleck
没错,我忘了它不包括介绍。 - Ondra Žižka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接