在阅读SQLite时,我在常见问题解答中偶然发现了这句话:"Threads are evil. Avoid them."
我非常尊重SQLite,所以我不能忽视这一点。根据“避免他们”政策,我想到了我还能做什么,而是使用它来平行我的任务。例如,我目前正在处理的应用程序需要一个始终响应的用户界面,并且需要不时地轮询多个网站(每个网站至少需要30秒)。
所以我打开了从这个常见问题解答中链接的PDF,基本上看起来该文件建议将几个技术一起与线程一起应用,例如障碍或事务性内存 - 而不是任何技术完全替换线程。
鉴于这些技术并没有完全免除线程(除非我误解了论文所说的内容),我可以看到两个选项:SQLite常见问题解答不是字面意思意味着什么,或者存在实际的方法,实际上完全避免使用线程。有没有?
关于tasklets /协作调度的快速说明作为替代方案 - 这在小例子中看起来很棒,但我想知道一个大型的UI重型应用程序是否可以以完全合作的方式实际并行化。如果您已成功完成此操作或知道此类示例,那么这肯定有资格作为有效答案!
答案 0 :(得分:6)
注意:此答案不再准确反映我对此主题的看法。我不喜欢它过于戏剧性,有点讨厌的语气。此外,我不太确定对可证明正确的软件的追求是如此无用,因为我似乎回想起来。我正在离开这个答案,因为它被接受,并被投票,并将其编辑成我目前认为会破坏它的东西。
我终于开始阅读这篇论文了。我从哪里开始?
作者正在唱一首老歌,如下所示:“如果你不能证明这个节目是正确的,那么我们都注定了!”当伴随着调制电吉他和快速鼓声大声尖叫时,这听起来是最好的。当计算机科学处于数学领域时,学者们开始唱这首歌,如果你没有证据,你就什么都没有。即使在第一个计算机科学系被从数学系解剖之后,他们仍然在唱这首歌。他们今天正在唱这首歌,没有人在听。为什么?因为我们其他人正在忙于创造有用的东西,所以软件中的好东西无法证明是正确的。
线程的存在使得证明程序更正确更难,但是谁在乎呢?即使没有线程,也只能证明最简单的程序是正确的。为什么我关心的是,在使用线程后,我的非平凡程序(无法证明是正确的)更难实现?我没有。
如果您不确定作者是否生活在一个学术梦想世界中,那么在他坚持认为他建议作为线程的替代品的协调语言最好用“视觉语法”来表达时,你可以肯定它(绘图)屏幕上的图表)。我以前从未听过这个建议,除了我职业生涯的每一年。只能通过GUI操作并且不能使用任何程序员常用工具的语言并不是一种改进。作者继续引用UML作为视觉语法的一个光辉的例子,它“通常与C ++和Java结合”。经常在什么世界?
与此同时,我和许多其他程序员继续使用线程而没有那么多麻烦。如何充分安全地使用线程几乎是一个解决的问题,只要你没有把所有问题都挂在可证明上。
看。线程是一个大孩子的玩具,你需要知道一些理论和使用模式才能很好地使用它们。就像数据库,分布式处理或程序员每天成功使用的任何其他超级学校设备一样。但仅仅因为你无法证明它是正确的并不意味着它是错误的。
答案 1 :(得分:5)
正如我所读到的,SQLite FAQ中的陈述只是对未经证实的线程有多么困难的评论。这是作者的观点,它可能是有效的。但是,在我的意见中,说你永远不应该使用螺纹将婴儿扔出洗澡水。线程是一种工具。像所有工具一样,它们可以被使用,并且可以被滥用。我可以阅读他的论文,并确信线程是魔鬼,但我已成功使用它们,没有杀死小猫。
请记住,SQLite的编写尽可能轻巧且易于理解(从编码的角度来看),因此我认为线程与这种轻量级方法相反。
此外,SQLite并不适用于高度并发的环境。如果您有其中一个,那么最好使用像Postgres这样的企业级数据库。
答案 2 :(得分:2)
邪恶,但必要的邪恶。线程的高级抽象(例如.NET中的任务)正变得越来越普遍,但在大多数情况下,业界并没有试图找到避免线程的方法,只是让它更容易处理任何类型的复杂性。并发编程。
答案 3 :(得分:2)
如果你真的想要没有线程,你可以,只要你不调用任何可能阻止的函数。这可能是不可能的。
另一种方法是将您作为线程的任务实现为finite state machines。基本上,任务执行它可以立即执行的操作,然后进入下一个状态,等待事件,例如输入到达文件或计时器关闭。 X Windows以及大多数GUI工具包都支持这种风格。当某些事情发生时,他们会调用一个回调,它会做它需要做的事情并返回。对于FSM,回调检查以查看任务所处的状态以及事件是什么以确定立即执行的操作以及下一个状态将是什么。
假设您有一个需要接受套接字连接的应用,并且对于每个连接,解析命令行,执行一些代码并返回结果。然后任务就是监听套接字。当select()
(或Gtk +,或其他)告诉你套接字有东西需要读取时,你将它读入缓冲区,然后检查你是否有足够的输入缓冲来做某事。如果是这样,你会前进到“开始做某事”状态,否则你会处于“读线”状态。 (你“做”的可能是多种状态。)完成后,你的任务就会从缓冲区中删除该行并返回“读取行”状态。不需要线程或抢占。
这使您可以通过事件驱动来执行多线程。但是,如果您的状态机很复杂,那么您的代码很难维护得很快,并且您需要使用某种FSM管理库来将运行FSM的繁重工作与实际执行操作的代码分开
P.S。另一种在没有真正使用线程的情况下获取线程的方法是GNU Pth library。它不会抢占,但如果你真的不想处理线程,它是另一种选择。
答案 4 :(得分:2)
我注意到的一个趋势,至少在Cocoa领域,是框架的帮助。 Apple已经竭尽全力帮助开发人员解决并发编程相对困难的概念。我见过的一些事情:
不同粒度的线程。 Cocoa支持从使用NSLock和NSThread的posix线程(低级别)到面向对象的线程,到NSOperation等高级别的并行操作。根据您的任务,使用NSOperation等高级工具更容易,并完成工作。
通过API在幕后进行线程处理。可可中的大量UI和动画内容隐藏在API之后。您负责调用API方法并提供在辅助线程完成时执行的异步回调(例如某些动画的结束)。
OPENMP。有一些像openMP这样的工具允许你提供编译指示,这些编译指示向编译器描述某些任务可以安全地进行parelellized。例如,以独立方式迭代一组项目。
这个行业的一大推动似乎是为应用程序开发人员简化操作,并将大量的线程细节留给系统开发人员和框架开发人员。学术界正在推动正式化的模式。如上所述,你不能总是避免线程,但你的武器库中有越来越多的工具使它尽可能无痛。
答案 5 :(得分:2)
另一种方法可能是使用不同的并发模型而不是完全避免多线程(你必须以某种方式并行利用所有这些CPU核心)。
查看Clojure中使用的机制(例如agents,software transactional memory)。
答案 6 :(得分:2)
软件事务内存(STM)是一种很好的替代并发控制。它可以很好地扩展到多个处理器,并且没有传统并发控制机制的大多数问题。它是作为Haskell语言的一部分实现的。值得一试。虽然,我不知道这在SQLite的上下文中是如何适用的。
答案 7 :(得分:2)
线程的替代方案:
(有趣的是,这些技术中有一半是谷歌发明或推广的。)
另一件事是许多Web框架透明地使用多个线程/进程来处理请求,并且通常以这样的方式消除与多线程相关的问题(对于框架的用户),或者至少使线程相当不可见。 Web是无状态的,唯一的共享状态是会话状态(这不是真正的问题,因为根据定义,单个会话不会做并发事务),并且数据库中的数据已经对其多线程无意义排序出去给你。
值得注意的是,这些都是抽象。这些东西的底层实现仍然使用线程。但这仍然非常有用。以同样的方式使用汇编程序编写Web应用程序,您不会直接使用线程来编写任何重要的应用程序。设计一个使用线程的应用程序太复杂了,无法让人类处理。
答案 8 :(得分:1)
线程并不是唯一的并发模型。演员模型(Erlang,Scala)是一个有点不同的方法的例子。
答案 9 :(得分:1)
如果您的任务真的非常容易隔离,那么您可以使用流程代替线程,就像Chrome为其标签所做的那样。
否则,在单个进程中,没有线程就没有办法实现真正的并行性,因为如果你想要同时发生两件事,你至少需要两个协同程序(假设你有多个处理器/内核)当然是手;否则真正的并行是不可能的。)
线程程序的复杂性始终与线程将执行的任务的隔离程度有关。如果您确定这些线程永远不会使用相同的变量,那么运行多个线程没有问题。然后,现代语言中存在多个高级构造,以帮助同步对共享资源的访问。
这真的是一个应用问题。如果你的任务很简单,适合某种高级Task对象(取决于你的开发平台;你的里程可能会有所不同),那么使用任务队列是你最好的选择。我的经验法则是,如果你找不到一个很酷的名字给你的线程,那么它的任务就不足以证明一个线程的合理性(而不是任务进入一个操作队列)。
答案 10 :(得分:1)
线程让你有机会做一些邪恶的事情,特别是在不同的执行路径之间共享状态。但它们提供了很多便利;您不必跨进程边界进行昂贵的通信。此外,它们的开销更少。所以我认为它们非常好,使用得当。
我认为关键是在线程中尽可能少地共享数据;只是坚持同步数据。如果您尝试分享更多内容,则必须使用复杂的代码,这些代码在第一时间难以正确使用。
答案 11 :(得分:0)
避免线程的一种方法是多路复用 - 实质上,你创建一个类似于你自己管理的线程的轻量级机制。
事情并不总是可行的。在你的情况下,每个网站的30s轮询时间 - 它可以分成60个0.5秒,在它们之间你可以调用UI吗?如果没有,抱歉。
线程不是邪恶的,它们很容易射击你的脚。如果执行查询A需要30秒,然后执行查询B需要另外30秒,在线程中同时执行它们将花费120秒而不是60,因为线程开销,争取磁盘访问和各种瓶颈。
但是如果操作A包含5秒的活动和55秒的等待,随机混合,而操作B需要60秒的实际工作,那么在线程中执行它们可能需要70秒,而在执行它们时顺序执行它们需要70秒。
经验法则是:线程应该空闲并且大部分时间都在等待。它们适用于I / O,慢速读取,低优先级工作等。如果您想要性能,请使用多路复用,这需要更多的工作,但速度更快,效率更高,并且更少注意事项。 (同步线程和避免竞争条件是线程头痛的一个完全不同的章节......)