【问题标题】:Do really need a count lock on Multi threads with one CPU core?真的需要一个 CPU 内核的多线程计数锁吗?
【发布时间】:2012-09-14 11:03:36
【问题描述】:

如果我有一些代码看起来像这样(请忽略语法,我想在没有指定语言的情况下理解它):

count = 0

def countDown():
  count += 1

if __name__ == '__main__':
    thread1(countDown)
    thread2(countDown)
    thread3(countDown)

这里我有一个只有一个核心的CPU,我真的需要锁定变量count,以防它可能被其他线程覆盖。

不知道,但是如果语言很在意,请在Java、C和Python下解释一下,非常感谢。


谢谢大家,我现在明白我确实需要一把锁。但这里还有一个问题,我什么时候需要使用多线程?

由于CPU只会执行一个instructor,看来多线程管理线程切换需要更多时间,无法节省计算时间。

【问题讨论】:

  • 到时候你就知道了。

标签: multithreading operating-system thread-safety locking multiprocessing


【解决方案1】:

从技术上讲,一般来说是的。也许不是在这个特定的例子中。但是想象一下你的原子函数将包含几个指令。操作系统可以并且确实一次执行多个线程。它执行一个步骤中的一些步骤,然后切换回选择继续哪个进程/线程的操作系统。它可以启动所有线程并在它们之间切换。即使在一个 CPU 上。然后所有线程将在相同的内存地址上操作并共享变量。

编辑:回答第二个问题。 当你有一个核心时,我可以想象只有一种情况需要多线程。这是您的一个线程可以锁定并且您需要在此期间对其进行监视或执行其他操作的时候。一个实际的例子是服务器。如果您想同时为多个客户端提供服务,则需要在它们之间切换。如果您在队列中为他们提供服务,那么一个坏客户可能会挂起整个流程。

如果您正在执行计算,您可能会使用它来拆分 I/O 和计算。但它必须是一个非常极端的情况才能有用或需要。

【讨论】:

  • 好吧,操作系统不会在单核 CPU 上“一次执行多个线程”。它一次执行一个线程。每次调度程序将一个线程换成另一个线程时,这就是一个上下文切换。但是您的观点是正确的,一个线程可能会在其工作中途被挂起,然后另一个线程可能会尝试更改第一个线程正在使用的相同内存。想象一下,线程 1 设置了一个长整数值的前半部分,被切换出来,线程 2 设置了相同整数的前半部分,然后线程 1 返回并设置后半部分,但不知道前半部分在其下发生了变化。繁荣。
  • 程序可能需要单核 CPU 上的多个线程的原因有很多。例如,这将允许用户界面在后台工作完成时保持响应。它不会使整个程序更快,只是更有用。由于阻塞调用,Win32 的许多方面一直令人头疼,似乎占据了整个 UI。即使在单核 CPU 上,这些东西在多线程下也会表现得更好。
  • 嗯,操作系统不会在单核 CPU 上“一次执行多个线程”。 从逻辑上讲是这样。我从来没有说过它在一个 CPU 上物理上这样做。关于一个 CPU 的声明仅适用于系统可以启动所有线程并在它们之间切换的部分。这就是为什么我会说,当您设计具有许多线程的程序时,无论物理处理核心的数量如何,更安全/“首选”的方式是线程始终同时运行。因为您通常不知道上下文何时以及如何切换。当然你可以通过一些工具来确保它。
【解决方案2】:

对于最简单的示例,我们创建多个线程共享一个变量并在其上执行单个原子指令。无论任何线程在何处被中断,它的状态要么完全在共享资源指令之前,要么完全在之后。

在这种情况下,x86 增量是原子的,因此是线程安全的。您不需要锁来保持一致性或幂等性。

【讨论】:

  • 你确定在java或python中你可以说它的x86增量吗?这个例子非常精简。我会说在 99% 的情况下,单一的增量不会造成麻烦。但在解释型语言的情况下,我会担心过多的指令或循环。
  • 我应该增加更多的资格。根据语言的不同,您还需要指定变量是易失的,或者向编译器或解释器提供一些应该是原子的线索。
【解决方案3】:

是的,您可能还需要一把锁。你的countDown 代码可能编译成这样的:

load global variable "count" into register x
x = x + 1
save register x into global variable "count"

如果中间有线程切换,那你就有麻烦了。您实际上并不需要第二个核心来获得不良行为。

有时countDown 可能会编译为原子指令。比如x86上有这样的指令,但我无法保证编译器会使用它们(除了自己编写程序集)。

【讨论】:

    【解决方案4】:

    对于简单的事情,比如增加一个计数器,而不是使用锁,你可以在 c 中找到以线程安全方式执行操作的原子函数。 GCC 定义了这些原子内置函数,它们通常包含在每个特定环境中的公共函数调用中 http://gcc.gnu.org/onlinedocs/gcc-4.5.0/gcc/Atomic-Builtins.html

    Mac OS X 定义了这些,例如 https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html

    这些有可能比锁更有效,因为它们在功能上比锁更受限制。

    【讨论】:

      【解决方案5】:

      什么时候需要多线程?

      对我来说有两个不同的应用程序:

      • 当多个线程(理想情况下每个内核只有一个线程)长时间处理整个问题的一小部分时进行并行处理。所需的代码和数据很小,而且 - 在最好的情况下 - 将适合核心的 L1 和 L2 缓存。这里的瓶颈(如果性能很重要的话)将是内存带宽以及如何尽可能少地使用它。
      • 另一种情况是当程序的不同组件或多或少地相互独立运行并且处理要求随时间而变化时。一个示例可能是一个邮件 (SMTP) 服务器,它至少具有三个独立的组件:一个 SMTP 服务器,用于接收来自 SMTP 客户端的邮件,一个 SMTP 客户端,用于将邮件发送到其他 SMTP 服务器,以及一个名称客户端,用于查找真实地址SMTP 客户端应该发送邮件。

      【讨论】:

        【解决方案6】:

        其他发帖者已经很好地解释了锁定问题。

        另一个问题也相当简单 - 大多数应用程序都是多线程的,以通过可能阻塞的多个 I/O 流来提高 I/O 性能。我现在正在打字。浏览器必须响应鼠标和键盘上的网络活动和用户输入。通常,它必须“同时”做这两件事。用户输入和网络通信分别非常慢和慢 - 两者都阻塞。因此,GUI 和网络通信在不同的线程上运行。即使只有一个 CPU 内核也需要发生这种情况,并且不这样做会导致旧的“Windows 3.1”风格的“沙漏应用程序”,其中 GUI 通常没有响应。请注意,这个需要多个线程的问题也适用于异步 I/O——它看起来像是在一个线程上运行,但受到内核线程/池的支持——大多数阻塞被移到内核中。

        单核盒子就是这样。您不能使用多个线程来加速 CPU 密集型计算(事实上,您会意识到,您会减慢它们的速度),但您可以将它们用于高性能 I/O。当我们都拥有单核 Pentium 和 Windows 95 时,许多应用程序都是多线程的——以优化 I/O,而不是加快计算速度。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-08-18
          • 1970-01-01
          • 1970-01-01
          • 2016-04-17
          • 1970-01-01
          • 2019-04-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多