如何创建 shared_ptr 的线程安全缓存
How to create thread safe cache of 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()
的内容,它仍然可以 return 来自 weak_ptr::lock()
的非空指针,而其他线程是 运行 指向 [=15= 的析构函数] 对象。
我看到了一些解决方案,例如:
- 在缓存中存储
shared_ptr
s 并且 运行 一个单独的线程将
使用 use_count()==1
连续删除 shared_ptr
-s(我们称之为 Cleanup()
)。
- 在缓存中存储
shared_ptr
并要求消费者使用 shared_ptr<File>
的特殊包装器。这个包装器将 shared_ptr<File>
作为成员,并将 reset()
它在析构函数中,然后调用 Cleanup()
.
第一个解决方案有点矫枉过正。第二个解决方案需要重构我项目中的所有代码。这两种解决方案都对我不利。还有其他方法可以使其安全吗?
除非我误解了你所描述的场景,如果另一个线程运行 是 File 对象的析构函数。这就是共享指针和弱指针的逻辑。所以 - 您当前的解决方案在这方面应该是线程安全的;但是 - 当您添加或删除元素时,它可能是非线程安全的。阅读相关内容,例如,在这个问题中:C++ Thread-Safe Map .
我预计以下代码会失败。但事实并非如此。似乎 weak_ptr::lock()
不会 return 指向正在销毁过程中的对象的指针。如果是这样,这是一个最简单的解决方案,只需添加一个互斥锁,而不必担心 returning 死对象 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
我有一个读取大量文件的代码。可以缓存一些文件。消费者在请求文件时收到 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()
的内容,它仍然可以 return 来自 weak_ptr::lock()
的非空指针,而其他线程是 运行 指向 [=15= 的析构函数] 对象。
我看到了一些解决方案,例如:
- 在缓存中存储
shared_ptr
s 并且 运行 一个单独的线程将 使用use_count()==1
连续删除shared_ptr
-s(我们称之为Cleanup()
)。 - 在缓存中存储
shared_ptr
并要求消费者使用shared_ptr<File>
的特殊包装器。这个包装器将shared_ptr<File>
作为成员,并将reset()
它在析构函数中,然后调用Cleanup()
.
第一个解决方案有点矫枉过正。第二个解决方案需要重构我项目中的所有代码。这两种解决方案都对我不利。还有其他方法可以使其安全吗?
除非我误解了你所描述的场景,如果另一个线程运行 是 File 对象的析构函数。这就是共享指针和弱指针的逻辑。所以 - 您当前的解决方案在这方面应该是线程安全的;但是 - 当您添加或删除元素时,它可能是非线程安全的。阅读相关内容,例如,在这个问题中:C++ Thread-Safe Map .
我预计以下代码会失败。但事实并非如此。似乎 weak_ptr::lock()
不会 return 指向正在销毁过程中的对象的指针。如果是这样,这是一个最简单的解决方案,只需添加一个互斥锁,而不必担心 returning 死对象 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