【问题标题】:lock free concurrency example; why is it not safe?无锁并发示例;为什么不安全?
【发布时间】:2018-09-01 02:36:48
【问题描述】:

我正在尝试学习 C++ 中的并发性,并且在此过程中我正在尝试查看哪些有效,哪些无效。下面的示例设计得不好,我知道有更好的设计方法,但我想知道为什么线程 1 和线程 2 似乎能够在共享数组中相互覆盖。我认为对共享flag_atomic 变量的操作在共享idx_atomic 索引的加载和写入之上和之下的获取/释放语义将阻止线程1 和线程2 检索相同的索引值,而不管idx_atomic 操作如何内存标签?

作为参考,我使用的是 MSVC 和 x64。

#include <iostream>
#include <vector>
#include <atomic>
#include <thread>
#include <chrono>

using namespace std::chrono;    // for ""ms operator

const size_t c_size = 40;
std::vector<int> shared_array;
std::atomic<bool> sync_start_atomic = false;
std::atomic<bool> flag_atomic = false;
std::atomic<size_t> idx_atomic = 0;


void thread1_x() {
    bool expected_flag = false;
    size_t temp_idx = 0;
    while (!sync_start_atomic.load(std::memory_order_relaxed));
    for (size_t i = 0; i < (c_size / 2); ++i) {
        while (flag_atomic.compare_exchange_weak(expected_flag, true, std::memory_order_acq_rel, std::memory_order_acquire)) {
                expected_flag = false;
        }
        temp_idx = idx_atomic.load(std::memory_order_relaxed);
        idx_atomic.store((temp_idx + 1), std::memory_order_relaxed);
        flag_atomic.store(false, std::memory_order_release);
        shared_array[temp_idx] = i;

    }
}

void thread2_x() {
       bool expected_flag = false;
    size_t temp_idx = 0;
    while (!sync_start_atomic.load(std::memory_order_relaxed));
    for (size_t i = 0; i < (c_size / 2); ++i) {
        while (flag_atomic.compare_exchange_weak(expected_flag, true, std::memory_order_acq_rel, std::memory_order_acquire)) {
            expected_flag = false;
        }
        temp_idx = idx_atomic.load(std::memory_order_relaxed);
        idx_atomic.store((temp_idx + 1), std::memory_order_relaxed);
        flag_atomic.store(false, std::memory_order_release);
        shared_array[temp_idx] = i + 100;

    }
}

void main(){
    shared_array.reserve(c_size);
    shared_array.assign(c_size, 0);
    std::thread tn_1(thread1_x);
    std::thread tn_2(thread2_x);
    std::this_thread::sleep_for(60ms);
    sync_start_atomic.store(true, std::memory_order_relaxed);

    tn_1.join();
    tn_2.join();

    for (size_t i = 0; i < c_size; ++i) {
        std::cout << shared_array[i] << " ";
    }
    std::cout << "\n";
}

实际输出示例:

100, 1, 101, 2, 3, 102, 4, 103, 104, 6, 106, 8, 108, 9, 10, 109, 11, 110, 12, 111, 14, 112, 113, 16 , 17, 18, 115, 19, 116, 117, 118, 119, 0, 0, 0, 0, 0, 0, 0, 0。

预期输出示例:

0, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 1, 2, 114, 3, 115, 4, 5, 6, 7 , 8, 9, 10, 11, 12, 13, 14, 15, 116, 16, 117, 17, 118, 18, 119, 19。

【问题讨论】:

    标签: c++ concurrency lock-free


    【解决方案1】:

    您的示例输出表明两个线程同时访问idx_atomic,这表明您的flag_atomic 循环存在问题。您正在使用的条件检查是向后的。 compare_exchange_weak 将返回 flag_atomic == expected_flag 比较的结果 - 换句话说,它在值更新时返回 true。由于您想在发生这种情况时退出循环,因此比较应该是

    while (!flag_atomic.compare_exchange_weak(expected_flag, true, std::memory_order_acq_rel, std::memory_order_acquire))
    

    【讨论】:

    • 谢谢,现在说得通了 :)。我会将您的答案标记为答案,但我是新手,不知道怎么做?
    • @JoeOram:点击向上/向下投票箭头下方的复选框。
    • 我喜欢将compare_exchange 函数视为在它们成功时返回true(即当它们进行存储时)。因为这就是您使用它们的原因,而不是用于比较本身,而是作为一家要求您提供您认为旧值的商店。所以我会为你第一段的第二部分选择不同的措辞。
    • @PeterCordes 我已经改写了一下。
    • 我很好奇,我描述的方式与您自己的想法相符吗?还是您真的在== 比较方面考虑得更多? (实际上是按位相等,包括任何填充,或 +0.0 与 -0.0,有时与 == 不同)。
    猜你喜欢
    • 2019-10-27
    • 2017-10-08
    • 2017-04-28
    • 2014-06-22
    • 2014-02-25
    • 2010-11-24
    • 1970-01-01
    • 2014-12-04
    • 2010-10-26
    相关资源
    最近更新 更多