我有一个例程,用于加载和解析文件中的数据。有可能需要同时从两个位置检索同一文件中的数据,即在后台缓存过程中和用户请求中。
具体来说,我使用的是C ++ 11线程和互斥库。我们使用Visual C ++ 11(又名2012)进行编译,因此受到它缺乏的限制。
我天真的实现是这样的:
map<wstring,weak_ptr<DataStruct>> data_cache;
mutex data_cache_mutex;
shared_ptr<DataStruct> ParseDataFile(wstring file_path) {
auto data_ptr = make_shared<DataStruct>();
/* Parses and processes the data, may take a while */
return data_ptr;
}
shared_ptr<DataStruct> CreateStructFromData(wstring file_path) {
lock_guard<mutex> lock(data_cache_mutex);
auto cache_iter = data_cache.find(file_path);
if (cache_iter != end(data_cache)) {
auto data_ptr = cache_iter->second.lock();
if (data_ptr)
return data_ptr;
// reference died, remove it
data_cache.erase(cache_iter);
}
auto data_ptr = ParseDataFile(file_path);
if (data_ptr)
data_cache.emplace(make_pair(file_path, data_ptr));
return data_ptr;
}
我的目标是双重的:
我当前的方法存在的问题是它根本不允许并发解析多个文件。如果我理解将会发生什么,他们每个人都会锁定并最终线性处理,一次一个线程。它可能会从运行更改为线程先通过锁定的顺序,但最终结果是相同的。
我考虑过的一个解决方案是创建第二张地图:
map<wstring,mutex> data_parsing_mutex;
shared_ptr<DataStruct> ParseDataFile(wstring file_path) {
lock_guard<mutex> lock(data_parsing_mutex[file_path]);
/* etc. */
data_parsing_mutex.erase(file_path);
}
但现在我必须关注data_parsing_mutex
如何更新。所以我想我需要另一个互斥量?
map<wstring,mutex> data_parsing_mutex;
mutex data_parsing_mutex_mutex;
shared_ptr<DataStruct> ParseDataFile(wstring file_path) {
unique_lock<mutex> super_lock(data_parsing_mutex_mutex);
lock_guard<mutex> lock(data_parsing_mutex[file_path]);
super_lock.unlock();
/* etc. */
super_lock.lock();
data_parsing_mutex.erase(file_path);
}
事实上,考虑到这一点,除非我再次检查缓存,否则当用户请求后台进程尚未完成时,它不会避免必须对文件进行双重处理。
但到现在为止,我的蜘蛛般的感官正在说There must be a better way
。在那儿?期货,承诺或原子能帮助我吗?
答案 0 :(得分:1)
根据您的描述,听起来您正在尝试使用线程池进行DataStruct
的延迟初始化,以及引用计数缓存。 std::async
应该能够为此类提供大量必要的调度和同步。
使用std::async
,代码看起来像这样......
map<wstring,weak_ptr<DataStruct>> cache;
map<wstring,shared_future<shared_ptr<DataStruct>>> pending;
mutex cache_mutex, pending_mutex;
shared_ptr<DataStruct> ParseDataFromFile(wstring file) {
auto data_ptr = make_shared<DataStruct>();
/* Parses and processes the data, may take a while */
return data_ptr;
}
shared_ptr<DataStruct> CreateStructFromData(wstring file) {
shared_future<weak_ptr<DataStruct>> pf;
shared_ptr<DataStruct> ce;
{
lock_guard(cache_mutex);
auto ci = cache.find(file);
if (!(ci == cache.end() || ci->second.expired()))
return ci->second.lock();
}
{
lock_guard(pending_mutex);
auto fi = pending.find(file);
if (fi == pending.end() || fi.second.get().expired()) {
pf = async(ParseDataFromFile, file).share();
pending.insert(fi, make_pair(file, pf));
} else {
pf = pi->second;
}
}
pf.wait();
ce = pf.get();
{
lock_guard(cache_mutex);
auto ci = cache.find(file);
if (ci == cache.end() || ci->second.expired())
cache.insert(ci, make_pair(file, ce));
}
{
lock_guard(pending_mutex);
auto pi = pending.find(file);
if (pi != pending.end())
pending.erase(pi);
}
return ce;
}
这可能会稍微优化一下,但一般的想法应该是相同的。
答案 1 :(得分:0)
在典型的计算机上,尝试同时加载文件没什么意义,因为磁盘访问将成为瓶颈。相反,让单个线程加载文件(或使用异步I / O)并将解析分离到线程池会更好。然后将结果存储在共享容器中。
关于防止双重工作,你应该考虑这是否真的有必要。如果你只是因为过早优化而做到这一点,你可能会通过专注于使程序响应而不是高效来让用户更快乐。也就是说,确保用户快速获得他们要求的内容,即使这意味着要做双重工作。
OTOH,如果技术上没有解析文件两次,你可以跟踪共享容器中每个文件的状态(加载,解析,解析)。