2011-04-03 28 views
1

我记得在我上大学的课程中,我最喜欢的一个竞赛条件示例是其中一个简单的main()方法启动了两个线程,其中一个线程增加了一个共享(全局)一个变量,另一个递减。伪代码:双CPU机器上的线程合作

static int i = 10; 

main() { 
    new Thread(thread_run1).start(); 
    new Thread(thread_run2).start(); 
    waitForThreads(); 
    print("The value of i: " + i); 
} 

thread_run1 { 
    i++; 
} 

thread_run2 { 
    i--; 
} 

教授接着问什么i值一百万十亿数不胜数运行之后。 (如果它基本上是10以外的任何东西)。不熟悉多线程系统的学生回答说,100%的时间,print()声明总是将i报告为10.

这实际上是不正确的,因为我们的教授表明,作为3条语句的每个增量/减量语句实际上编译(组装):

1: move value of 'i' into register x 
2: add 1 to value in register x 
3: move value of register x into 'i' 

因此,i的值可以是9,10或11(I不会进入细节。)

我的问题:

这是(是?)我的理解是物理寄存器集是特定于处理器的。在使用双CPU机器时(注意双核和双CPU之间的区别),每个CPU都有自己的一组物理寄存器吗? 我以为答案是肯定的。

在单CPU(多线程)机器上,上下文切换允许每个线程拥有自己的虚拟寄存器集。由于双CPU机器上有两套物理寄存器,因此不会因竞争条件而导致更大的潜在可能性,因为您可以从字面上同时运行两个线程,而不是单线程上的“虚拟”同时操作, CPU机器? (虚拟同时操作是指参考每个上下文开关保存/恢复寄存器状态的事实)。

更具体地说 - 如果你是在一个8-CPU机器上运行它,每个带有一个线程的CPU都是竞态的条件消除了?如果将此示例展开为使用8个线程,则在双CPU机器上,每个具有4个内核的CPU可能会增加或减少竞争条件? 操作系统如何防止装配指令的step 3在两个不同的CPU上同时运行?

回答

1

是的,引入双核CPU导致大量潜在线程化程序失败。调度程序通过多任务单核CPU快速切换线程之间的线程上下文。它消除了一类与陈旧的CPU缓存相关的线程错误。

尽管如此,您给出的示例也可能会在单个内核上失败。当线程调度程序中断线程时,就像它将变量的值加载到寄存器中以增加线程一样。它不会失败,因为调度程序中断线程的可能性并不大。

有一个操作系统功能,可以让这些程序无论如何都能够在几分钟内瘫痪而不是崩溃。称为'处理器亲和性',可作为Windows上start.exe的AFFINITY命令行选项,winapi中的SetProcessAfinityMask()。查看Interlocked类的助手方法,该方法可以自动递增和递减变量。

+0

被选为多CPU和多核之间差异的答案,以及关于在单核上失败的注释。甚至没有考虑过,这是一个很好的观点。 – 2011-04-03 23:35:50

1

你仍然有竞争条件 - 它根本没有改变。设想两个核心同时执行增量 - 它们都会加载相同的值,递增到相同的值,然后存储相同的值...因此,这两个操作的总增量将是一个而不是两个。

的潜在问题的其他原因,其中的内存模型而言 - 在步骤1中可能没有真正检索i最新值,并在某种程度上步骤3中可能不会立即写i新值其他线程可以看到。

基本上,这一切都变得非常棘手 - 这就是为什么它通常是一个好主意,访问共享数据使用已被写入由专家谁真正知道无锁更高层次的抽象当任使用同步他们在做什么。

+0

当然 - 除了Java的Collections.synchronizedMap/Set/List/Collection方法之类的更高级别的结构之外,我熟悉同步技术(互斥锁,信号量,同步块)。 - 我只是想知道,如果一个写得不好的程序在双CPU机器和双核机器上的功能是不同的。 - 我想我毕竟是漫不经心,我​​真的只是在寻找我最后一个问题的答案,我相信你回答了。 :) – 2011-04-03 17:45:45

1

首先,双处理器与双核处理器没有实际影响。双核处理器在芯片上仍然有两个完全独立的处理器。他们可能共享一些缓存,并共享一条公共总线到内存/外设,但处理器本身是完全独立的。 (双线程单代码,例如超线程)是第三种变体 - 但它也有一套每个虚拟处理器的寄存器。两个处理器共享一组执行资源,但它们保留完全独立的寄存器组。

其次,实际上只有两种情况真的很有趣:一个执行线程,以及其他一切。一旦你有多个线程(即使所有的线程都在单个处理器上运行),你也会遇到同样的潜在问题,就好像你在一个拥有数千个处理器的大型机器上运行一样。现在,当代码在更多的处理器上运行时(尽可能多地创建线程),您很可能会发现问题很快就会显现出来,但问题本身并没有/没有完全改变。

从实践的角度来看,从测试的角度来看,拥有更多内核是非常有用的。考虑到典型操作系统上任务切换的粒度,编写代码将非常容易,该代码将在不会在单个处理器上出现问题的情况下运行,在运行时会在几小时甚至几分钟内崩溃并烧毁两个或更多物理处理器。但问题并没有真正改变 - 当你拥有更多的处理器时,这个问题更有可能更快地显示出来。

最终,竞争条件(或死锁,活锁等)是关于代码的设计的,关于其运行的硬件的而不是。硬件可以在执行相关条件时采取哪些步骤,但有关差异与简单数量的处理器无关。相反,它们是关于诸如当您不仅仅拥有多个处理器的单个机器而是具有完全独立地址空间的多台机器时所做出的让步之类的事情,因此您可能需要采取额外步骤来确保在向存储器写入值时对于无法直接看到该内存的其他机器上的CPU,它将变得可见。