0

我在线程上遇到了这个小问题。

    int x = 0;
    add() {

      x=x+1;
    }

如果我们在多个线程中运行它,比如 4 个线程,那么每次 x=4 的最终值或者它可能是 1、2、3 或 4。

谢谢

PS可以说添加的原子操作是这样的,

LOAD A x
ADD A 1 
LOAD x A

那么最终结果将是 4。我是对的还是我错了?

4

3 回答 3

3

这是数据竞争的经典例子。

现在,让我们仔细看看 add() 的作用:

add()
{
   x = x + 1;
}

这转化为:

  1. 给我 X 的最新值并将其存储在我的私人工作区中
  2. 将 1 添加到存储在我的私人工作区中的那个值
  3. 将我在工作区中的内容复制到我从中复制的内存(可全局访问)。

现在,在我们进一步解释这一点之前,您有一个称为上下文切换的东西,它是您的操作系统在不同线程和进程之间分配处理器时间的过程。此过程通常为您的线程提供有限的处理器时间(在 Windows 上约为 40 毫秒),然后中断该工作,复制处理器在其寄存器中的所有内容(并因此保留其状态)并切换到下一个任务。这称为循环任务调度

您无法控制您的处理何时会被中断并转移到另一个线程。

现在想象你有两个线程在做同样的事情:

1. Give me the most recent value of X and store it in my private workspace
2. Add 1 to that value that is stored in my private workspace
3. Copy what I have in my workspace to the memory that I copied from (that is globally accessible).

并且 X 在它们中的任何一个运行之前等于 1。

第一个线程可能会执行第一条指令,并将其工作时最近的 X 值存储在其私有工作区中 - 1。然后发生上下文切换,操作系统中断您的线程并将控制权交给队列中的下一个任务,恰好是第二个线程。第二个线程还读取等于 1 的 X 的值。

第二个线程设法运行完成 - 它将“下载”的值加 1 并“上传”计算的值。

操作系统再次强制进行上下文切换。

现在第一个线程在它被中断的地方继续执行。它仍然会认为最近的值是 1,它会将该值加一并将其计算结果保存到该内存区域。这就是数据竞争的发生方式。您期望最终结果是 3,但它是 2。

有很多方法可以避免这个问题,例如锁/互斥锁比较和交换原子操作

于 2012-07-05T13:03:55.980 回答
2

您的代码在两个级别上被破坏:

  1. 线程的动作之间没有happens-before强加关系;
  2. 未强制执行获取和增量的原子性。

解决1.可以加volatile修饰符。这仍然会使操作成为非原子操作。为了确保原子性,您将(最好)使用AtomicIntegersynchronized(涉及锁定,不是首选)。

就目前而言,如果从不参与递增的线程读取结果,则结果可以是0 到 4 之间的任何数字。

于 2012-07-05T13:22:51.357 回答
2

多线程应用程序是并发的(这是重点)。

t1: LOAD A1 x
t2: LOAD A2 x
t3: LOAD A3 x
t4: LOAD A4 x
t1: ADD A1 1 
t2: ADD A2 1 
t3: ADD A3 1 
t4: ADD A4 1 
t1: STORE x A1
t2: STORE x A2
t3: STORE x A3
t4: STORE x A4

A1、A2、A3、A4 是本地寄存器。

结果是1,但也可能是234。如果您有另一个线程,由于可见性问题,它可能会看到旧值并查看0

于 2012-07-05T13:23:33.470 回答