【问题标题】:Can Multiple Threads Read The Same Class Member Variable?多个线程可以读取同一个类成员变量吗?
【发布时间】:2019-12-05 17:35:26
【问题描述】:

多个线程能否在不产生竞争条件的情况下安全地读取同一个类成员变量?

class foo {
    int x;
};

void Thread1(foo* bar) {
    float j = bar->x * 5;
}

void Thread2(foo* bar) {
    float k = bar->x / 5;
}

例如,如果我们有两个线程运行Thread1Thread2。如果每个线程都传递了 same foo 对象,它们是否可以独立运行,没有竞争条件,因为我们只读取变量而不写入?还是访问对象的行为使整个事情变得不安全?

如果上述安全的,只要不触及foo::x,第三个线程是否可以安全地写入同一个foo对象?

#include <thread>

class foo {
public:
    int x = 1;
    int y = 1;
};

void Thread1(foo* bar) {
    int j;
    for (int i = 0; i < 1000; i++) {
        j = bar->x * 5;
    }
    printf("T1 - %i\n", j);
}

void Thread2(foo* bar) {
    int k;
    for (int i = 0; i < 1000; i++) {
        k = bar->x / 5;
    }
    printf("T2 - %i\n", k);
}

void Thread3(foo* bar) {
    for (int i = 0; i < 1000; i++) {
        bar->y += 3;
    }
    printf("T3 - %i\n", bar->y);
}

int main() {
    foo bar;

    std::thread t1(Thread1, &bar);
    std::thread t2(Thread2, &bar);
    std::thread t3(Thread3, &bar);

    t1.join();
    t2.join();
    t3.join();

    printf("x %i, y %i\n", bar.x, bar.y);

    return 0;
}

【问题讨论】:

  • 是的,我坚信如此。当然,这包括 foo::x 不能“间接”写成,例如通过以某种方式完全覆盖foo
  • 您可以从任意数量的线程中读取相同的变量。只有写入需要同步。
  • 线程的开始是一个足够的障碍。在线程开始之后(以及在加入之前)对某个地址/范围的读取访问是安全的,并且不需要保护,只要没有其他内容写入该特定地址/范围。
  • 在当今大多数处理器中,默认情况下读取对齐的 int 是原子操作,因此它不会造成伤害,但仍然在 C++ 中使用 std::atomic 和相应的内存以更明确的方式表达它栅栏参数将不胜感激。
  • @DeanSeo 与对齐的 int 和原子操作有什么关系,如果我唯一要做的就是阅读?即使它是一个大数组,如果根本没有写入,也不会有竞争条件?

标签: c++ thread-safety


【解决方案1】:

多个线程能否安全读取同一个类成员变量 没有创建竞争条件?

是和不是。

- 您提供的代码不会导致竞争条件,因为当您有至少 2 个线程在同一共享资源上工作并且其中至少一个线程正在写入时,可能会发生竞争条件到那个资源。

- 您的代码不被认为是线程安全的,因为它公开了 x 和 y 成员以供读写,并使其成为可能(对于您或其他使用您的代码的程序员)代码)导致竞争条件。 您依赖于您的知识(随着时间的推移您可能会忘记) x 应该只被读取,而不是写入,并且 y 应该只由单个线程写入。您应该通过在 critical code sections 中创建 mutual exclusion 来强制执行此操作。

如果您希望线程仅从 x 和 y 读取,则应将该类设为 immutable

【讨论】:

  • 最后一句:或者使用std::atomic&lt;int&gt;
  • @MikevanDyke std::atomic 只是另一个创建互斥的 C++ 工具
  • @SubMachine 是的,原子可以使用来创建互斥和关键代码段,但使用原子变量本身并不暗示那些。 Mike 是对的 - 使用原子是一种有效的替代方案,并且可能是首选,因为互斥体/关键部分的成本往往要高几个数量级。
  • 我喜欢你的第二部分。这可以实现,例如通过将成员 foo::x 公开为对线程的 const 引用(或仅作为复制值)。我发现用 C++ 建模更困难的是:数据在线程的生命周期内是不可变的。
  • 临界区是可能导致额外性能影响的东西。因此,我知道 OP 会仔细询问真正需要多少安全性。 (这是在选择 C++ 的一般假设下,因为您可以细粒度地管理在哪里使用什么来获得最大性能。尽可能快地编写防弹多线程代码绝对是一个错误的选择。);- )
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-14
  • 2015-01-05
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
  • 2015-06-28
  • 1970-01-01
相关资源
最近更新 更多