数据竞争
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据竞争相关的知识,希望对你有一定的参考价值。
参考技术A 数据竞争(data race),是多个 goroutine 写一个key 导致数据不一致。在 golang 中一般使用 -race 参数检测程序的竞争条件;
举个例子
启动 5 个 goroutine 修改 n 的值,执行命令
go run -race main.go
找到一个竞争条件,并且已报告行号,接下来尝试第一次修改:
增加 sync.Mutex 的互斥锁,保护变量 n ,执行 go run -race main.go 结果 5 ,看来起效果了!那有没有其他方式方法呢?
接下来第二次尝试
执行 go run -race main.go 结果 5 ,看来 atomic 也可以对变量进行保护。
是否有其他方式?还带模式!
https://go.dev/doc/articles/race_detector
AtomicIntegerArray 中的数据竞争
【中文标题】AtomicIntegerArray 中的数据竞争【英文标题】:Data Races in an AtomicIntegerArray 【发布时间】:2016-11-02 01:47:17 【问题描述】:在下面的代码中:
我在 2 个线程中更新 num[1]=0
的 AtomicIntegerArray
num 1000 次。
在主线程的 2 个线程的末尾;num[1]
的值不应该是 2000,因为在 AtomicIntegerArray
中不应该有数据竞争。
但是我得到的随机值
代码:
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArr
private static AtomicIntegerArray num= new AtomicIntegerArray(2);
public static void main(String[] args) throws InterruptedException
Thread t1 = new Thread(new MyRun1());
Thread t2 = new Thread(new MyRun2());
num.set(0, 10);
num.set(1, 0);
System.out.println("In Main num before:"+num.get(1));
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("In Main num after:"+num.get(1));
static class MyRun1 implements Runnable
public void run()
for (int i = 0; i < 1000; i++)
num.set(1,num.get(1)+1);
static class MyRun2 implements Runnable
public void run()
for (int i = 0; i < 1000; i++)
num.set(1,num.get(1)+1);
编辑:添加 num.compareAndSet(1, num.get(1), num.get(1)+1);
而不是 num.set(1,num.get(1)+1);
也不起作用。
【问题讨论】:
【参考方案1】:我得到的随机值
这叫the lost-update problem。
因为,在下面的代码中:
num.set(1, num.get(1) + 1);
虽然涉及的每个单独的操作都是原子的,但组合操作不是。来自两个线程的单个操作可以交错,导致来自一个线程的更新被另一个线程用过时的值覆盖。
你可以使用compareAndSet
来解决这个问题,但是你必须检查操作是否成功,失败时再做一次。
int v;
do
v = num.get(1);
while (!num.compareAndSet(1, v, v+1));
还有一种方法可以达到这个目的:
num.accumulateAndGet(1, 1, (x, d)->x+d);
accumulateAndGet(int i, int x, IntBinaryOperator accumulatorFunction)
使用将给定函数应用于当前值和给定值的结果以原子方式更新索引 i 处的元素,并返回更新后的值。该函数应该没有副作用,因为当尝试更新由于线程之间的争用而失败时,它可能会被重新应用。该函数应用索引 i 处的当前值作为其第一个参数,并将给定的更新作为第二个参数。
【讨论】:
嗨,卢克:是的,整个操作不是原子的!我的错 !!!至于解决它,我知道循环的 CompareAndSet 但不知道可用的累积和获取方法。使用 GetAndIncrement 也解决了它!谢谢:) !!【参考方案2】:这是一个经典的比赛条件。任何时候你有一个 fetch、一个操作和一个 put,你的代码都是活泼的。
考虑两个线程,它们都在大致“同时”执行num.set(1,num.get(1)+1)
。首先,让我们分解一下表达式本身在做什么:
num.get(1)
;让我们称之为x
将其加 1;让我们称之为y
它将总和放入 `num.set(1, y);
即使表达式中的中间值只是堆栈上的值,而不是显式变量,但操作是相同的:get、add、put。
好的,回到我们的两个线程。如果操作是这样排序的呢?
inital state: n[1] = 5
Thread A | Thread B
========================
x = n[1] = 5 |
| x = n[1] = 5
| y = 5 + 1 = 6
y = 5 + 1 = 6 |
n[1] = 6 |
| n[1] = 6
由于两个线程都在其中一个线程放入其附加值之前获取了该值,因此它们都做同样的事情。你有两次 5 + 1,结果是 6,而不是 7!
你想要的是getAndIncrement(int idx)
,或者类似的方法之一,可以原子地进行get、add和put操作。
这些方法实际上都可以构建在您确定的compareAndSet
方法之上。但要做到这一点,您需要在循环中进行增量,直到compareAndSet
返回 true。此外,为了使其工作,您将初始 num.get(1)
值存储在局部变量中,而不是第二次获取它。实际上,这个循环说“继续尝试 get-add-put 逻辑,直到它在没有其他人在操作之间竞争的情况下工作。”在我上面的示例中,线程 B 会注意到 compareAndSet(1, 5, 6)
失败(因为当时的实际值是 6,而不是预期的 5),因此重试。这实际上是所有这些原子方法(如 getAndIncrement
)所做的。
【讨论】:
num.set(1,num.get(1)+1);我的印象是 set 将完全以原子方式运行,这是真的,但在实际调用 set 之前;可以在第二个线程中调用另一个 get ,然后可以发生 2 组。 getandIncrement 确实以原子方式获取、递增和设置;而在我的情况下;这可能会出现(发生;再次发生,然后是 2 组)。谢谢 Yshavit :)以上是关于数据竞争的主要内容,如果未能解决你的问题,请参考以下文章
R语言使用survival包的coxph函数在竞争风险分析的加权数据集上进行回归模型构建使用coxph函数对加权数据集进行竞争风险模型拟合