具有互斥锁的getter和setter何时才是线程安全的?

时间:2017-06-04 21:25:25

标签: c++ multithreading pthreads

考虑以下课程:

class testThreads
{
private:
    int var; // variable to be modified
    std::mutex mtx; // mutex
public:
    void set_var(int arg) // setter
    {
        std::lock_guard<std::mutex> lk(mtx);
        var = arg;
    }

    int get_var() // getter
    {
        std::lock_guard<std::mutex> lk(mtx);
        return var;
    }

    void hundred_adder()
    {
        for(int i = 0; i < 100; i++)
        {
            int got = get_var();
            set_var(got + 1);
            sleep(0.1);
        }
     }
};

当我在main()中创建两个线程时,每个线程都有hundred_adder的线程函数修改相同的变量var,var的最终结果总是不同,即不是200而是其他一些数字。

从概念上讲,为什么使用带有getter和setter函数的互斥锁不是线程安全的?锁定防护装置是否无法阻止比赛状况变化?什么是替代解决方案?

5 个答案:

答案 0 :(得分:4)

Thread a: get 0
Thread b: get 0
Thread a: set 1
Thread b: set 1

瞧,即使它应该是2,var也是1。

显然你需要锁定整个操作:

for(int i = 0; i < 100; i++){
     std::lock_guard<std::mutex> lk(mtx);
     var += 1;
}

或者,您可以将变量设为原子(即使是在您的情况下也可以放松)。

答案 1 :(得分:3)

  int got = get_var();
  set_var(got + 1);

您的get_var()set_var()本身是线程安全的。但是get_var()后跟set_var()的组合序列不是。没有互斥体可以保护整个序列。

您有多个并发线程执行此操作。您有多个线程正在调用get_var()。在第一个完成它并解锁互斥锁之后,另一个线程可以立即锁定互斥锁并获得与第一个线程所做的got相同的值。绝对没有什么可以防止多个线程同时锁定并获得相同的got

然后两个线程都会调用set_var(),将受互斥锁保护的int更新为相同的值。

这只是可能发生的一种可能性。您可以轻松地让多个线程按顺序获取互斥锁,从而将var递增几个值,然后在几秒钟之前调用get_var()的其他一些停滞的线程,然后才转到调用set_var(),从而将var重置为更小的值。

答案 2 :(得分:2)

代码在线程安全中显示,它永远不会设置或获取变量的部分值。

但是您对方法的使用并不能保证值会正确更改:从多个线程读取和写入可能会相互冲突。两个线程读取值(11),都将它递增(到12)并且都设置为相同(12) - 现在你计算了2但实际上只增加了一次。

修复选项:

  • 提供“安全增量”操作
  • 提供相当于InterlockedCompareExchange的内容,以确保您更新的值与原始值相对应,并在必要时重试
  • 将调用代码包装到单独的互斥锁中或使用其他同步机制来阻止操作混合。

答案 3 :(得分:0)

为什么不直接使用std :: atomic作为共享数据(在本例中为var)?这将更安全有效。

答案 4 :(得分:0)

这绝对经典。 一个线程获取var的值,释放mutex,另一个线程在第一个线程有机会更新之前获得相同的值。

因此,该过程可能会失去增量。

有三个明显的解决方案:

void testThreads::inc_var(){
    std::lock_guard<std::mutex> lk(mtx);
    ++var;
}

这是安全的,因为互斥锁一直保持到变量更新为止。

接下来:

bool testThreads::compare_and_inc_var(int val){
    std::lock_guard<std::mutex> lk(mtx);
    if(var!=val) return false;
    ++var;
    return true;
}

Then write code like:

   int val;
   do{
       val=get_var();
   }while(!compare_and_inc_var(val));

这是有效的,因为循环重复,直到它确认它正在更新它读取的值。这可能会导致实时锁定,但在这种情况下它必须是瞬态的,因为线程只能因为另一个线程而无法取得进展。

最后将int var替换为std::atomic<int> var,然后使用++varvar.compare_exchange(val,val+1)var.fetch_add(1);进行更新。 注意:注意compare_exchange(var,var+1)无效......

++保证在std::atomic<>类型上是原子的,但尽管“看起来”像一般操作,但int不存在此类保证。

std::atomic<>还提供了适当的内存屏障(以及提示需要何种屏障的方法),以确保正确的线程间通信。

std::atomic<>应该是可用的无等待,无锁实现。检查您的文档和标记is_lock_free()