为什么线程被认为是"邪恶的"?

pie*_*fou 23 unix multithreading thread-safety

我正在阅读SQLite FAQ,并发现了这段话:

线程是邪恶的.避免他们.

我不太明白"线程是邪恶的"这句话.如果这是真的,那么替代方案是什么?

我对线程的肤浅理解是:

  • 线程会发生并发.否则,CPU功率将被浪费,等待(例如)慢速I/O.
  • 但最糟糕的是,您必须同步逻辑以避免争用,并且必须保护共享资源.

注意:由于我不熟悉Windows上的线程,我希望讨论仅限于Linux/Unix线程.

Pav*_*aev 14

当人们说"线程是邪恶的"时,通常会在"流程很好"的背景下这样做.线程隐式共享所有应用程序状态和句柄(并且线程本地是选择加入).这意味着在访问共享数据时,有很多机会忘记同步(或者甚至不了解您需要同步!).

进程具有单独的内存空间,它们之间的任何通信都是显式的.此外,用于进程间通信的原语通常使您根本不需要同步(例如管道).如果需要,您仍然可以使用共享内存直接共享状态,但在每个给定的实例中也是如此.因此,犯错的机会较少,而且代码的意图更明确.

  • 本文并未真正将流程作为替代方案进行讨论 (6认同)

Sea*_*ods 12

简单回答我理解的方式......

大多数线程模型使用"共享状态并发",这意味着两个执行进程可以同时共享同一个内存.如果一个线程不知道对方在做什么,它可以以另一个线程不期望的方式修改数据.这会导致错误.

线程是"邪恶"的,因为你需要用你的头脑周围n的线程在同一时间同一内存中的所有的工作,所有的好玩的东西与它(死锁,比赛条件等)去.

您可能会阅读关于Clojure(不可变数据结构)和Erlang(消息传递)并发模型的关于如何实现类似目的的替代思想.


Jer*_*ner 11

让线程变得"邪恶"的是,一旦你在程序中引入了多个执行流,你就不能再指望你的程序以确定的方式运行.

也就是说:给定相同的输入集,单线程程序(在大多数情况下)总是会做同样的事情.

多线程程序在给定相同的输入集的情况下,每次运行时都可以执行不同的操作,除非它被非常小心地控制.这是因为不同线程运行不同代码位的顺序由OS的线程调度程序与系统计时器结合确定,这为程序运行时的内容引入了大量"随机性".

结果是:调试多线程程序比调试单线程程序要困难得多,因为如果你不知道自己在做什么,那么很容易就会遇到竞争条件或死锁错误每月一次或两次随机出现(看似).该程序看起来很适合你的QA部门(因为他们没有一个月的时间来运行它)但是一旦它出现在现场,你会听到客户说程序崩溃了,没有人可以重现崩溃. .. bleah.

总而言之,线程并不是真正的"邪恶",但它们是强大的juju并且不应该被使用,除非(a)你真的需要它们和(b)你知道你正在做什么.如果您确实使用它们,请尽可能少地使用它们,并尝试尽可能简单地使它们的行为变得简单.特别是对于多线程,如果有什么可能出错,它(迟早)会.


cle*_*tus 7

我会用另一种方式解释它.并不是线程是邪恶的,而是多线程环境中的副作用是邪恶的(这不是很容易说).

此上下文中的副作用是影响由多个线程共享的状态,无论是全局还是仅共享.我最近写了一篇关于Spring Batch评论,其中一个代码片段是:

private static Map<Long, JobExecution> executionsById = TransactionAwareProxyFactory.createTransactionalMap();
private static long currentId = 0;

public void saveJobExecution(JobExecution jobExecution) {
  Assert.isTrue(jobExecution.getId() == null);
  Long newId = currentId++;
  jobExecution.setId(newId);
  jobExecution.incrementVersion();
  executionsById.put(newId, copy(jobExecution));
}
Run Code Online (Sandbox Code Playgroud)

现在,在这里不到10行代码中至少存在三个严重的线程问题.在此上下文中的副作用的示例将是更新currentId静态变量.

函数式编程(Haskell,Scheme,Ocaml,Lisp等)倾向于支持"纯"函数.纯函数是没有副作用的函数.许多命令式语言(例如Java,C#)也鼓励使用不可变对象(不可变对象是一旦创建状态就不能改变的对象).

这两件事的原因(或至少是其影响)基本相同:它们使多线程代码容易.根据定义,纯函数是线程安全的.根据定义,不可变对象是线程安全的.

优势过程具有较少的共享状态(通常).在传统的UNIX C编程中,执行fork()来创建一个新进程会导致共享进程状态,这被用作IPC(进程间通信)的一种手段,但通常用(用exec())替换状态别的.

但是线程的创建和销毁便宜得多,而且它们占用的系统资源也更少(实际上,操作本身可能没有线程概念,但你仍然可以创建多线程程序).这些被称为绿色线程.


jal*_*alf 6

您链接的论文似乎很好地解释了自己。你读过它吗?

请记住,线程可以引用编程语言结构(在大多数过程或 OOP 语言中,您手动创建一个线程,并告诉它执行一个函数),或者它们可以引用硬件结构(每个 CPU 内核一次执行一个线程)。

硬件级线程显然是不可避免的,这就是CPU的工作原理。但是 CPU 并不关心您的源代码中如何表达并发性。例如,它不必通过“beginthread”函数调用。操作系统和 CPU 只需要被告知应该执行哪些指令线程。

他的观点是,如果我们使用比 C 或 Java 更好的语言以及为并发设计的编程模型,我们基本上可以免费获得并发。如果我们使用一种消息传递语言,或者一种没有副作用的函数式语言,编译器将能够为我们并行化我们的代码。它会起作用。


geo*_*war 5

螺纹比锤子,螺丝刀或任何其他工具更“邪恶”。他们只需要技能就可以利用。解决方案不是避免它们;而是避免它们。是要教育自己和提高技能。