目录
2、使用std::call_once 和 std::once_flag
在编写代码的过程中,有些执行函数只要求被执行一次即可,但是又需要暴露出接口给用户,为了避免函数中的代码被多次执行,找到了一些方法来解决该问题。
1、使用静态标识符
使用标识符是一种全手动的方法,不拘泥于任何C++版本,例如
#include <iostream>
void executeOnce() {
static bool executed = false; // 静态局部变量
if (!executed) {
std::cout << "函数被执行" << std::endl;
executed = true; // 标记为已执行
} else {
std::cout << "函数进不去" << std::endl;
}
}
int main() {
executeOnce(); // 第一次调用
executeOnce(); // 第二次调用
return 0;
}
静态局部变量在第一次进入函数时初始化,并且在整个程序运行期间保持其值。这种方法简单且高效。
这种方法在单线程环境中是有效的,但在多线程环境中可能会出现问题,因为静态局部变量的初始化和修改不是线程安全的。
例如:
#include <iostream>
void executeOnce() {
static bool executed = false; // 静态局部变量
if (!executed) {
std::cout << "函数被执行" << std::endl;
executed = true; // 标记为已执行
} else {
std::cout << "函数进不去" << std::endl;
}
}
nt main() {
std::thread t1(executeOnce);
std::thread t2(executeOnce);
t1.join(); // 等待线程 t1 完成
t2.join(); // 等待线程 t2 完成
return 0;
}
有可能导致如下的结果
- 线程A 检查
executed
为false
。 - 线程B 也检查
executed
为false
。 - 线程A 设置
executed
为true
并执行函数体。 - 线程B 设置
executed
为true
并执行函数体。
这样会导致函数被多次执行,违背了只执行一次的要求。
2、使用std::call_once
和 std::once_flag
为了确保在多线程环境中函数只执行一次,可以使用 C++ 标准库中的 std::call_once
和 std::once_flag
。这是C++11的特性,需要支持C++11。这些工具提供了线程安全的方式来保证某个函数或代码块只执行一次。
#include <iostream>
#include <mutex>
// 定义一个全局的 std::once_flag 变量
std::once_flag onceFlag;
void executeOnce() {
std::call_once(onceFlag, []() {
std::cout << "函数被调用" << std::endl;
// 这里放置需要只执行一次的代码
});
}
int main() {
std::thread t1(executeOnce);
std::thread t2(executeOnce);
t1.join(); // 等待线程 t1 完成
t2.join(); // 等待线程 t2 完成
return 0;
}
解释:
std::once_flag
:这是一个标志变量,用于与std::call_once
配合使用,以确保某段代码只执行一次。std::call_once
:这个函数接受一个std::once_flag
和一个可调用对象(如 lambda 表达式)。它会保证传入的可调用对象只会被执行一次,即使在多线程环境中也是如此。
std::once_flag 是一个特殊的类型,专门用于配合 std::call_once 使用。它内部实现了一些同步机制,确保即使在多线程环境下也能正确地保证只执行一次。
std::call_once 函数内部会自动处理锁和其他同步机制,避免了竞态条件的发生。
总结:如果你确信你的应用是单线程的,或者你可以保证 executeOnce 函数不会在多线程环境下被调用,那么使用静态局部变量的方法仍然是有效的。但在多线程环境中,必须使用线程安全的方式,如 std::call_once 和 std::once_flag,并且可以在多个对象之间共享,以确保无论多少个对象调用该函数,它都只会执行一次。或者不使用全局的once_flag,而使用static修饰也可以。