如何在 main 之前预加载数据
Posted
技术标签:
【中文标题】如何在 main 之前预加载数据【英文标题】:How to preload data before main 【发布时间】:2013-02-21 22:47:58 【问题描述】:我的目标是快速处理各种竞争条件,如果在大约同一时间在 2 个单独的线程中调用给定函数时可能会导致问题。我的快速修复是通过在 main() 之前调用它们来保证函数已经被初始化。这是我想出的解决方案,但我觉得我可能会重新发明***。 MSVC2010 STL 中是否有可用的选项? (还没有提升)或者是否有更好的方法来快速处理这些问题,而无需在这种情况下为每个函数添加重要的线程安全代码?
template <typename T, T func>
struct PreLoaderHelper
PreLoaderHelper()
wcout << L"Preload helper constructor" << endl;
func();
;
template <typename T, T func>
struct PreLoader
static PreLoaderHelper<T, func> _helper;
;
template <typename T, T func>
PreLoaderHelper<T, func> PreLoader<T, func>::_helper;
#define PRELOAD(fn) template struct PreLoader<decltype(&fn), fn>;
void foo() wcout << L"inside foo" << endl;
void bar() wcout << L"inside bar" << endl;
vector<wstring> & foobar()
static vector<wstring> sPresidents;
if(sPresidents.size() == 0)
sPresidents.push_back(L"George Washington");
sPresidents.push_back(L"John Addams");
sPresidents.push_back(L"Thomas Jefferson");
return sPresidents;
wstring foofoo(const wstring &) wcout << L"inside foofoo" << endl; return L"foofoo";
PRELOAD(foo);
PRELOAD(bar);
PRELOAD(foobar);
PRELOAD(foo);
int main()
return 0;
【问题讨论】:
编程真的很棒,每个人的想法完全不同。你能解释一下为什么不使用互斥锁吗? MSVC2010 是否支持std::call_once
?但是,如果我看到并发代码访问相同数据而不使用互斥锁或原子,我总是会怀疑。此外,我们可以看到您的解决方案,但看不到您的实际问题。你的解决方案最好在codereview.stackexchange.com。
速度主要是写和执行。创建大量互斥锁(每个函数一个)并为每个互斥锁添加锁定将花费我更长的时间。此外,通过在每次调用特定函数时锁定互斥锁,执行时间会显着延长。我可以通过双重检查锁定实现来解决这个问题,但是要为我选择的平台正确实现每个功能需要付出更多的努力。 @Zeta,我会更新一点
是否会调用PRELOAD
避免给定函数大约在同一时间在 2 个单独的线程中调用?
@KenKin 是的 - 这会导致函数作为全局(静态)对象的构造函数的一部分被调用一次;由于此类对象是在 main 之前初始化的,因此它应该保证我们不会遇到竞争条件,除非某个地方的另一个类似构造以相同的方式启动线程。
【参考方案1】:
第一个问题:你之前真的要打电话给他们吗 进入主线?为什么不把它们称为 main 中的第一件事, 在开始任何线程之前?
否则:经典的习惯用法是在初始化器中使用它们 到静态变量。通常的方法是从 构造函数;如果您有额外的数据必须是 初始化,这无疑是最好的方法。如果不, 像这样简单的东西:
static bool initialized = (function(), true);
会成功的。
形式上,这仅保证它们将被初始化 在使用同一翻译单元中的任何其他内容之前, 但实际上,这将保证调用该函数 在 main 之前,或者在加载 DLL 期间,如果它在 DLL 中 除了带有 main 的那个。
【讨论】:
是的,这基本上就是我想出的,但在某种程度上我不需要用一堆额外的变量名称污染任何命名空间(或者期望一个全局的“初始化”变量外部定义)。至于为什么不调用 main,我喜欢将此逻辑保留在函数旁边的想法,因此稍后查看代码的人不会想知道“如果我的 2 个线程尝试一起初始化怎么办?” @Rollie 如果您真的担心会污染某些命名空间,请为每次初始化创建一个特殊的命名空间。 (namespace STRCONCAT( ForInitialization, __LINE__ )
在匿名命名空间中,如果你真的很偏执的话。)恕我直言,将其设为静态或将其置于匿名命名空间中就足够了。
@Rollie 最后:这种初始化通常与特定的类有关。如果是这样,使用类的私有静态成员变量将使名称尽可能保持私有。
@LokiAstari 你无法防范所有可能的愚蠢行为。
他们总是会制造更好的白痴。我考虑了 STRCONCAT 解决方案,我倾向于回避这类事情。此外,模板解决方案可防止同一函数的多次初始化。不过,你的答案很好【参考方案2】:
你可以这样做:
int dummy = (foo(), (void)0, bar(), 0);
int main()
// foo() and bar() have already been called
此外,C++11 保证以下变体仅导致一次调用,无竞争:
void call_foo()
static int dummy = (call_foo(), 0);
void some_thread_function() call_foo();
【讨论】:
哦,很好地使用了逗号运算符。 VC10 没有我想的那么合规,所以我认为你的第二部分不会起作用。【参考方案3】:如果您使用的是 C++11,请记住:
-
静态函数变量初始化是线程安全的。
您可以使用列表初始化语义。
试试:
std::vector<std::wstring>& plop1()
// Thread safe
// Guaranteed to be done once
// No setup required
// No cost if not used.
static std::vector<std::wstring> sPresidents =
L"George Washington",
L"John Addams",
L"Thomas Jefferson"
;
return sPresidents;
【讨论】:
哦,我多么希望这是可能的...尝试了几次战斗,但没有成功。而且 VC11 在最新的补丁中添加了很多其他不错的功能【参考方案4】:我强烈建议根据您的情况使用关键部分进行适当的同步。进入和退出临界区不会添加很多代码,并且可以优雅地处理这种情况。
如果您不想继续在 main() 之前初始化函数的原始方法,您可以使用全局变量初始化,因为它发生在 main 函数调用之前。在
上有一篇关于这种方法的好文章http://blog.fishingcactus.com/index.php/2009/01/28/fixing-c-static-and-global-variable-initialization/#sthash.pmBtrYD8.dpbs
【讨论】:
以上是关于如何在 main 之前预加载数据的主要内容,如果未能解决你的问题,请参考以下文章