数据竞争

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]=0AtomicIntegerArray 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 :)

以上是关于数据竞争的主要内容,如果未能解决你的问题,请参考以下文章

模拟竞争风险数据

GO并发编程的数据竞争问题

R语言使用survival包的coxph函数在竞争风险分析的加权数据集上进行回归模型构建使用coxph函数对加权数据集进行竞争风险模型拟合

利用爬虫技术盗用他人数据构成不正当竞争 || 以案说法

使用数据库事务防止竞争条件 (Laravel)

核心数据保存竞争条件错误