如何创建shared_ptr的线程安全缓存

时间:2018-06-20 13:57:26

标签: c++ multithreading shared-ptr

我有一个读取很多文件的代码。可以缓存某些文件。消费者索要文件时收到shared_ptr。如果文件仍在内存中,其他使用者可以请求此文件并从缓存中获取它。如果文件不在内存中,它将被加载并放入缓存。

简化代码:

struct File
{
    File(std::string);
    bool AllowCache() const;
};

typedef std::shared_ptr<File> SharedPtr;
typedef std::weak_ptr<File> WeakPtr;
std::map<std::string, WeakPtr> Cache;

SharedPtr GetFile(std::wstring Name)
{
    auto Found = Cache.find(Name);
    if (Found != Cache.end())
        if (auto Exist = Found->second.lock())
            return Exist;

    auto New = boost::make_shared<File>(Name);
    if (New->AllowCache())
        Cache[Name] = New;
    return New;
}

我的问题是:如何使此代码安全?即使我用互斥锁保护GetFile()的内容,当其他线程正在运行指向weak_ptr::lock()对象的析构函数时,它仍然可以从File返回非空指针。

我看到了一些解决方案,例如:

  1. 在缓存中存储shared_ptr并运行一个单独的线程,该线程将 持续用shared_ptr删除use_count()==1 -s(我们叫它Cleanup())。
  2. shared_ptr存储在缓存中,并要求使用者使用shared_ptr<File>的特殊包装。该包装器将有shared_ptr<File>作为成员,并将在析构函数中reset()进行包装,然后调用Cleanup()

第一个解决方案有些矫kill过正。第二种解决方案要求重构我项目中的所有代码。两种解决方案都对我不利。还有其他方法可以使其安全吗?

2 个答案:

答案 0 :(得分:1)

除非我误解了您所描述的情况,否则如果另一个线程正在运行一个lock()的析构函数,则WeakPtr的{​​{1}}将会失败(即返回一个伪shared_ptr)。文件对象。这就是共享和弱指针的逻辑。所以-您当前的解决方案在这方面应该是线程安全的;但是-在您添加或删除元素时,它可能不是线程安全的。阅读有关此问题的信息,例如C++ Thread-Safe Map

答案 1 :(得分:1)

我希望以下代码会失败。但事实并非如此。看来weak_ptr::lock()不会返回指向正在销毁过程中的对象的指针。如果是这样,仅添加互斥对象而不用担心weak_ptr::lock()返回死对象是最简单的解决方案。

char const *TestPath = "file.xml";

void Log(char const *Message, std::string const &Name, void const *File)
{
    std::cout << Message
        << ": " << Name
        << " at memory=" << File
        << ", thread=" << std::this_thread::get_id()
        << std::endl;
}

void Sleep(int Seconds)
{
    std::this_thread::sleep_for(std::chrono::seconds(Seconds));
}

struct File
{
    File(std::string Name) : Name(Name)
    {
        Log("created", Name, this);
    }

    ~File()
    {
        Log("destroying", Name, this);
        Sleep(5);
        Log("destroyed", Name, this);
    }

    std::string Name;
};

std::map<std::string, std::weak_ptr<File>> Cache;
std::mutex Mutex;

std::shared_ptr<File> GetFile(std::string Name)
{
    std::unique_lock<std::mutex> Lock(Mutex); // locking is added
    auto Found = Cache.find(Name);
    if (Found != Cache.end())
        if (auto Exist = Found->second.lock())
        {
            Log("found in cache", Name, Exist.get());
            return Exist;
        }

    auto New = std::make_shared<File>(Name);
    Cache[Name] = New;
    return New;
}

void Thread2()
{
    auto File = GetFile(TestPath);
    //Sleep(3); // uncomment to share file with main thead
}

int main()
{
    std::thread thread(&Thread2);
    Sleep(1);
    auto File = GetFile(TestPath);
    thread.join();
    return 0;
}

我的期望:

thread2: created
thread2: destroying
thread1: found in cache <--- fail. dead object :(
thread2: destroyed

VS2017结果:

thread2: created
thread2: destroying
thread1: created <--- old object is not re-used! great ;)
thread2: destroyed