1

我目前正试图解决使用 C++ STL 容器的线程安全问题。我最近尝试通过使用 std::mutex 作为成员变量来实现线程安全的 std::vector,然后才意识到虽然我可以通过锁定锁使成员函数成为线程安全的,但我无法使 lib 函数像 std::sort 线程安全,因为它们只获得 begin()/end() 迭代器,这是一般 STL 中容器和算法之间基本分离的结果。

所以我想,如果我不能使用锁,软件事务内存(STM)怎么样?

所以现在我坚持这个:

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

#define LIMIT 10

std::atomic<bool> start{false};
std::vector<char> vec;

void thread(char c)
{
    while (!start)
        std::this_thread::yield();

    for (int i = 0; i < LIMIT; ++i) {
        __transaction_atomic {
        vec.push_back(c);
        }
    }
}

int main()
{
    std::thread t1(thread, '*');
    std::thread t2(thread, '@');

    start.store(true);

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

    for (auto i = vec.begin(); i != vec.end(); ++i)
        std::cout << *i;

    std::cout << std::endl;

    return EXIT_SUCCESS;
}

我编译的是:

g++ -std=c++11 -fgnu-tm -Wall

使用 g++ 4.8.2,这给了我以下错误:

error: unsafe function call to push_back within atomic transaction

我有点明白了,因为 push_back 或 sort 或任何未声明 transaction_safe 的东西,但这给我留下了以下问题:

a) 我该如何解决这个错误?

b) 如果我无法修复该错误,那么这些事务块通常用于什么?

c) 如何实现无锁线程安全向量?!

提前致谢!

编辑: 感谢到目前为止的答案,但他们并没有真正抓到我的痒。让我举个例子:假设我有一个全局向量,并且对这个向量的访问应该在多个线程之间共享。所有线程都尝试进行排序插入,因此它们生成一个随机数并尝试以排序方式将此数字插入向量中,因此向量始终保持排序状态(包括 c 的重复项)。为了进行排序插入,他们使用 std::lower_bound 来查找要插入的“索引”,然后使用 vector.insert() 进行插入。

如果我为包含 std::mutex 作为成员的 std::vector 编写包装器,那么我可以编写包装器函数,例如使用 std::lock_guard 锁定互斥锁然后执行实际 std::vector 的插入。插入()调用。但是 std::lower_bound 并不关心成员互斥锁。这是一个功能,而不是一个错误 afaik。

这使我的线程陷入困境,因为其他线程可以在某人做他的 lower_bound 事情时更改向量。

我能想到的唯一解决方法是:忘记包装器并为向量设置一个全局互斥锁。每当有人想对这个向量做任何事情时,他都需要那个锁。

那就是问题所在。使用此全局互斥锁有哪些替代方法。这就是软件事务内存浮现在脑海中的地方。

那么现在:如何在 STL 容器上使用 STM?(和a),b),c)从上面)。

4

3 回答 3

2

我相信使 STL 容器 100% 线程安全的唯一方法是将其包装在您自己的对象中(保持实际容器私有)并在您的对象中使用适当的锁定(互斥锁,等等)以防止多线程访问 STL 容器。

这相当于在每个容器操作周围的调用者中锁定一个互斥锁。

为了使容器真正线程安全,您必须处理容器代码,这是没有规定的。

编辑:还有一个注意事项 - 请注意您为包装对象提供的接口。您不能很好地分发对存储对象的引用,因为这将允许调用者绕过包装器的锁定。所以你不能只是用互斥锁复制向量的接口并期望事情能正常工作。

于 2013-12-18T20:38:44.713 回答
0

我不确定我理解为什么你不能使用互斥锁。如果每次访问向量时都锁定互斥锁,那么无论您执行什么操作,都可以确定一次只有一个线程在使用它。根据您对安全向量的需求,肯定有改进的空间,但互斥锁应该是完全可行的。

锁定互斥锁 -> 调用 std::sort 或任何你需要的 -> 解锁互斥锁

如果另一方面你想要在你的类上使用 std::sort ,那么这又是一个通过容器的迭代器提供线程安全访问和读取方法的问题,因为那些是 std:: sort 无论如何都需要使用才能对向量进行排序,因为它不是容器或任何类型的朋友。

于 2013-12-18T20:20:44.373 回答
0

您可以使用简单的互斥锁来使您的类线程安全。如另一个答案所述,您需要在使用前使用互斥锁来锁定向量,然后在使用后解锁。

警告! 所有的 STL 函数都可以抛出异常。如果您使用简单的互斥锁,如果任何函数抛出,您将遇到问题,因为互斥锁不会被释放。为避免此问题,请将互斥锁包装在一个在析构函数中释放它的类中。这是一个很好的编程实践来学习:http ://c2.com/cgi/wiki?ResourceAcquisitionIsInitialization

于 2013-12-18T20:49:14.813 回答