std::call_once 和 std::once_flag 是 C++11 中引入的线程安全的函数和类型,用于确保某个函数只被调用一次。
std::call_once的函数原型如下:
template<class Callable, class... Args>
void call_once(std::once_flag& flag, Callable&& func, Args&&... args);
参数说明:
- flag:一个std::once_flag对象,用于标记函数是否已经被调用过。它必须通过引用传递给 std::call_once 函数,以确保在多线程环境下仅仅执行一次。
- func:要调用的函数或可调用对象。(可以是函数、lambda 表达式等)
- args:传递给func的参数。
使用std::call_once时,需要先创建一个std::once_flag对象,并将其作为参数传递给std::call_once函数。当多个线程同时调用std::call_once时,只有一个线程会执行func函数,其他线程会被阻塞,直到func函数执行完毕。
使用 std::call_once 和 std::once_flag 可以避免在多线程环境下多次执行同一个函数,从而提高程序性能和正确性。
注意事项
-
确保只调用一次:std::call_once用于保证某个函数只被调用一次。因此,在使用std::call_once时,需要确保只有一个线程能够调用该函数。可以通过std::once_flag来实现线程安全的调用。
-
函数参数传递:std::call_once只能调用无参函数,如果需要传递参数,可以使用lambda表达式或者std::bind来包装函数。
stdcall_once只能调用无参函数的原因是为了简化实现和确保线程安全性:
函数被调用时,stdcall_once会检查一个标志位,如果标志位为假,则执行函数并将标志位设置为真。这样可以确保函数只会被执行一次。然而,如果函数带有参数,那么在多线程环境下,参数的传递和处理可能会引发竞态条件和线程安全问题。
在std::call_once实现中使用了函数指针来保存要调用的函数, 同时为了确保线程安全性,stdcall_once使用了一些同步机制,如互斥锁或原子操作,来保证只有一个线程可以执行被调用的函数。这些同步机制对于有参函数来说可能会引入更多的复杂性和潜在的线程安全问题。 -
异常处理:如果被调用的函数抛出异常,std::call_once会将异常传递给调用者。因此,在使用std::call_once时,需要注意异常处理,确保程序的稳定性。
-
线程同步:std::call_once会阻塞其他线程,直到被调用的函数执行完毕。因此,在使用std::call_once时,需要注意线程同步,确保其他线程不会受到阻塞影响。
使用场景主要包括以下几个方面:
-
线程安全的单例模式:在多线程环境下,使用std::call_once可以确保只有一个线程执行初始化操作,从而实现线程安全的单例模式。
-
延迟初始化:有些资源在程序运行过程中可能不会被立即使用,而是在需要时才进行初始化。使用std::call_once可以保证只有在第一次需要时才进行初始化,避免了不必要的开销。
-
缓存数据的初始化:在某些情况下,需要对一些数据进行缓存以提高性能。使用std::call_once可以确保只有在第一次需要时才进行缓存数据的初始化,避免了重复计算或加载数据的开销。
-
全局变量的初始化:在多个源文件中使用同一个全局变量时,可以使用std::call_once来确保全局变量只被初始化一次,避免了重复定义和初始化的问题。
单例模式:
class Singleton {
private:
static Singleton* instance;
static std::once_flag flag;
Singleton() {}
public:
static Singleton* getInstance() {
std::call_once(flag, []() {
instance = new Singleton();
});
return instance;
}
};
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::flag;
void main() {
Singleton* obj1 = Singleton::getInstance();
Singleton* obj2 = Singleton::getInstance();
// obj1和obj2指向同一个实例
}
延迟初始化:
class LazyInitialization {
private:
int data;
std::once_flag flag;
void lazyInit() { data = 100; }
public:
int getData() {
std::call_once(flag, &LazyInitialization::lazyInit, this);
return data;
}
};
void main() {
LazyInitialization obj;
std::cout << "Data: " << obj.getData() << std::endl;
}
缓存的初始化:
std::unordered_map<int, std::string> cache;
std::once_flag flag;
void initCache() {
// 初始化缓存
cache = "Value1";
cache = "Value2";
}
std::string getValue(int key) {
std::call_once(flag, initCache);
return cache[key];
}
void main() {
std::cout << getValue(1) << std::endl;
std::cout << getValue(2) << std::endl;
// 使用缓存
}
全局变量的初始化:
std::string globalStr;
std::once_flag flag;
void initGlobalStr() {
globalStr = "Initialized";
}
void main() {
std::call_once(flag, initGlobalStr);
// 使用globalStr
}
使用 lambda 表达式:
std::once_flag flag;
void main() {
int count{};
std::call_once(flag, [this, &count](){
count = 10;
});
// 其他代码...
}
带参传递,并使用 lambda 表达式:
std::once_flag flag;
void foo(int x, double y) {
std::cout << "foo called with x = " << x << " and y = " << y << std::endl;
}
void main() {
int x = 10;
double y = 3.14;
std::call_once(flag, [x, y]() {
foo(x, y);
});
}
带参传递,并使用std:bind:
std::once_flag flag;
void foo(int x, double y) {
std::cout << "foo called with x = " << x << " and y = " << y << std::endl;
}
void main() {
int x = 10;
double y = 3.14;
std::call_once(flag, std::bind(foo, x, y));
}