我在线程上遇到了这个小问题。
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.我是对的还是我错了什么?
答案 0 :(得分:3)
这是数据竞赛的典型例子。
现在,让我们仔细看看add()的作用:
add()
{
x = x + 1;
}
这转换为:
现在,在我们进一步解释之前,你有一个名为context switch的东西,这是你的操作系统在不同的线程和进程之间划分处理器时间的过程。这个过程通常会给你的线程一个有限的处理器时间(在windows上大约40毫秒),然后中断工作,复制处理器在其寄存器中的所有内容(从而保留它的状态)并切换到下一个任务。这称为Round-robin task scheduling。
您无法控制何时处理中断并转移到另一个线程。
现在想象你有两个线程做同样的事情:
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.然后发生上下文切换,操作系统中断您的线程并给出控制到队列中的下一个任务,恰好是第二个线程。第二个线程还读取X的值,该值等于1.
第二个线程设法运行完成 - 它将“下载”和“上传”计算值的值加1。
操作系统再次强制进行上下文切换。
现在第一个线程在被中断的点继续执行。它仍然会认为最近的值是1,它会将该值递增1并将其计算结果保存到该内存区域。这就是数据竞争的发生方式。您希望最终结果为3,但它是2。
有许多方法可以避免此问题,例如locks/mutexes,compare and swap或atomic operations。
答案 1 :(得分:2)
您的代码分为两个级别:
happens-before
关系; 要解决1.您可以添加volatile
修饰符。这仍将使操作非原子化。为确保原子性,您可以使用(最好)AtomicInteger
或synchronized
(涉及锁定,不是首选)。
按照目前的情况,如果从没有参与递增的线程中读取,结果可以是 0到4之间的任何数字。
答案 2 :(得分: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
,但可能是2
,3
或4
。如果您有其他线程,则由于可见性问题可能会看到旧值,请参阅0