多线程访问unordered_map的运行时错误

时间:2016-02-26 12:14:23

标签: c++ multithreading singleton

我有这个多线程代码,它试图使用stl unordered_map创建一个线程本地单例对象。

这是code。我在这里逐字复制代码:

#include <iostream>
#include <unordered_map>
#include <vector>
#include <thread>
#include <algorithm>
#include <mutex>
using namespace std;

class single
{
public:
    // Every thread needs to call this to get its individual instance
    static single* getInstance( unsigned int threadId );

    static void print( unsigned int threadId )
    {
        std::cout << "threadId:"    << threadId << ", Singleton: " << _instances[threadId] << std::endl;
    }

protected:
    // Made protected not private so that singleton can be subclassed
    single();                                           // Clients cant construct objects directly
    ~single();                                          // cannot be destroyed by clients

    single(const single &) = delete;                    // non-copyable
    single& operator=(const single &) = delete;         // can't be copy assigned
    single(single &&) = delete;                         // non-move constructible
    single & operator=(single && ) = delete;            // non-move assignable

private:
    static std::unordered_map<unsigned,single*> _instances;
    static std::mutex _lock;

};

std::mutex single::_lock;
std::unordered_map<unsigned,single*> single::_instances;

single::single(){}

single::~single(){}

single* single::getInstance( unsigned int threadId )
{
    if( _instances.count( threadId ) == 0 )
    {
        std::lock_guard<std::mutex> lg(_lock);
        if( _instances.count( threadId ) == 0 )
        {
            _instances[threadId] = new single;
            std::cout <<"Created By ThreadId: " << threadId <<std::endl;
        }
    }

    return _instances[threadId];
}

void Run( unsigned int threadId )
{
    single::getInstance(threadId)->print(threadId);
}

int main()
{
    std::vector<std::thread> workers;
    const unsigned threadCount = 16;

    for( auto i = 0; i != threadCount; ++i )
    {
        workers.push_back( std::thread( Run, i ) );
    }

    for_each( workers.begin(), workers.end(), std::mem_fn(&thread::join) );

    return 0;
}

我使用unordered_map的{​​{1}}常量函数(线程安全?)来检查线程的实例是否已创建。如果计数为0,我创建实例并将其作为密钥的值存储为count()。我还添加了一个threadId来防止多个线程并发插入到无序映射静态对象,但似乎它们是一些我无法发现的竞争条件,这些代码有时会出错。

任何人都可以向我解释这段代码的哪一部分正在发生竞争条件,是否可以使这个解决方案适用于线程本地单例?

1 个答案:

答案 0 :(得分:3)

  

任何人都可以向我解释这段代码的哪一部分正在发生竞争......

不,它不是线程安全的。一旦有至少一个写入者,所有访问都需要被锁保护。因此,第一个_instances.count()无人看守,可能导致数据竞争。

  

...如果这个解决方案可以用于线程本地单例?

您可以使用thread_local来控制本地线程存储持续时间。

您还可以将锁定保护程序迁移到unordered_map上的任何访问权限之前。

single* single::getInstance( unsigned int threadId )
{
    std::lock_guard<std::mutex> lg(_lock);
    if( _instances.count( threadId ) == 0 )
    {
        _instances[threadId] = new single;
        std::cout <<"Created By ThreadId: " << threadId <<std::endl;
    }
    return _instances[threadId];
}

Demo code here.

关于线程计数的附注,根据要解决的问题,您可以将线程数限制为std::thread::hardware_concurrency限制。