【问题标题】:Can I make a C++11 class/object universally thread safe?我可以使 C++11 类/对象普遍线程安全吗?
【发布时间】:2017-09-02 08:11:54
【问题描述】:

阅读了Can mutex implementations be interchanged (independently of the thread implementation),据我了解,通过使用 C++ 11 互斥锁,我可以确保我的类对于所有线程实现都是线程安全的

我的理解正确吗?我期待是(可能有警告)或否(有原因)。

  • 我的代码可能需要在目标平台上重新编译,但不需要对我的源代码进行任何更改。 (@彼得)
  • 或者说,我应该能够针对特定平台进行编译并提供一个可以安全地与任何线程库一起使用的 .so?

鉴于投反对票,我会尽量做到更准确。

互斥体的 C++11 实现是否独立任何其他线程 API? C ++ 11互斥锁是否只知道彼此并独立于执行线程如何到达该点而实现阻塞。

C++11 互斥锁是否总是与 OpenMP 一起工作(需要重新编译)?无论团队 B 使用何种特定线程 API,团队 A 是否可以提供一个线程安全 API(使用 C++ 互斥体)供团队 B 使用?所有代码都可以重新编译。

我附上了一个工作示例。

#include <iostream>
#include <omp.h>
#include <mutex>
class A {
    unsigned n = 0;

public:

    void inc() {
        ++n;
    }

    unsigned get() {
        return n;
    }
};

class B {
    std::mutex mutex;
    unsigned n = 0;

public:

    void inc() {
        mutex.lock();
        ++n;
        mutex.unlock();

    }

    unsigned get() {
        return n;
    }
};

int main() {

    A a;
    B b;
#pragma omp parallel for
    for (int i = 0; i < 100000; i++) {
        a.inc();
        b.inc();
    }

    std::cout << a.get() << " " << b.get() << std::endl;

}

我上次运行的输出是:

98015 100000

这是运气还是设计?

【问题讨论】:

  • 你想要什么作为答案?
  • 是或否。我认为答案是肯定的,但我可能弄错了。如果没有,那是什么原因?
  • @Passer By 你知道答案吗?
  • 我引用的问题提出了一个不同的问题,我从该帖子中给出的答案中推断出我的问题的答案。但我并不确定。我必须把头放在一个街区上,所以我只是检查一下。
  • 这取决于你所说的实现的“交换”是什么意思。如果您的意思是您的代码可以从一个实现(编译器及其标准库)移植到另一个实现,那么答案是肯定的。如果您的意思是使用一个编译器编译代码并链接到与另一个编译器关联的标准库,那么答案是“否”。

标签: c++ multithreading


【解决方案1】:

如果您将类的所有成员设为私有并且对受任何同步机制(std::mutexstd::atomic 或 boost /Qt/whatever),无论使用何种线程 API。

最简单的方法是对类内的所有数据访问操作(getter/setter)使用单个std::recursive_mutex/std::unique_lock 对,并将数据成员设为私有。它只是保证不管线程 API 是什么都能正常工作,但在性能方面可能不是最佳的。

大多数其他解决方案可能适合或不适合您的具体情况。有时根本不需要同步原语。

【讨论】:

  • 谢谢 Andrei R。这是我的理解。我会等待一段时间让任何人反驳你,然后将其发布为答案(将你的头放在块上:)。
  • 这是正确的,假设受保护的代码保证在任何情况下都不会重新输入(在 recursive_mutex 的情况下 - 除非它被设计为不会重新输入)。例如。举个反例:某种纤程/任务 API,例如 boost::fiber,一个函数获取一个互斥体,然后调用一些纤程感知的异步 API; API 切换当前线程以运行另一个纤程,该纤程又调用相同的函数,试图再次获得相同的互斥锁。这将导致死锁(对于快速互斥锁)或无法预料的“并发”数据访问(对于递归互斥锁)。
  • 一般来说,“可能死锁”并不意味着“不是线程安全的”,这是一种不同的错误
  • @Andrey Turkin 我很欣赏并会注意到您的评论,但我认为 Andrei R 是正确的,在我的问题的精神之内。
【解决方案2】:

您始终可以使用互斥锁使类的成员函数在没有数据竞争的情况下进行交互。但这还不足以确保一个对象可以在多个线程中使用而不会发生冲突;您需要更广泛的设计。例如,考虑写出向量的内容:

int size = vec.size();
for (int i = 0; i < size; ++i)
    std::cout << vec[i] << ' ';
std::cout << '\n';

使用内部互斥体,对vec.size() 的调用和对vec::operator[] 的调用没有数据争用,因此从多个线程调用它们是“安全的”。但是这段代码有一个问题:如果另一个线程从向量中删除一个元素,那么向量会比原来的小,这个循环会跑到最后。

【讨论】:

  • 假设我已经使用 C++11 互斥锁完全控制了我的对象,并且完全控制了您要突出显示的问题(这似乎是我通过互斥锁控制的标准线程问题),将互斥锁适用于 std::threads openmp 线程,以及任何其他线程实现?
猜你喜欢
  • 2016-03-26
  • 2017-11-15
  • 1970-01-01
  • 2012-11-30
  • 2015-11-21
  • 2013-02-23
  • 2013-09-20
  • 2020-10-03
  • 1970-01-01
相关资源
最近更新 更多