我有一个读取很多文件的代码。可以缓存某些文件。消费者索要文件时收到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
返回非空指针。
我看到了一些解决方案,例如:
shared_ptr
并运行一个单独的线程,该线程将
持续用shared_ptr
删除use_count()==1
-s(我们叫它Cleanup()
)。shared_ptr
存储在缓存中,并要求使用者使用shared_ptr<File>
的特殊包装。该包装器将有shared_ptr<File>
作为成员,并将在析构函数中reset()
进行包装,然后调用Cleanup()
。第一个解决方案有些矫kill过正。第二种解决方案要求重构我项目中的所有代码。两种解决方案都对我不利。还有其他方法可以使其安全吗?
答案 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