Note

call_once 配合 once_flag 一起使用,用于在多个线程中只执行某段初始化代码一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include<iostream>
#include<thread>
#include<mutex>

// 使用call_once解决单例模式线程安全问题
class Log {

private:
Log() { std::cout << "Log is constructed\n";}; // 私有构造函数,防止外部实例化
Log(const Log&) = delete; // 显式删除了拷贝构造函数,编译器生成的默认构造函数会被禁用
Log& operator=(const Log&) = delete; // 删除拷贝构造和赋值运算符,防止对象被拷贝
static std::once_flag ONCE;
static Log* log; // 静态成员函数只能直接访问静态成员,必须在类内声明
static void init() { // 静态成员函数不属于具体对象实例,只属于类本身,所以没有 this 指针,因此必须定义 log指针 为静态成员变量
if (!log) { log = new Log(); }
}

public:
//静态方法不需要实例化对象就可以调用,确保了全局只有一个实例
static Log& GetInstance() {

std::call_once(ONCE, init); // init() 函数只会被调用一次,后续线程等待后直接跳过
// std::call_once(ONCE, []() {if (!log) log = new Log(); }); lambda 表达式可以直接访问静态变量(还有全局变量)

return *log;
}

void PrintLog(std::string msg) {
static std::mutex cout_mutex; // 输出流加锁保护
std::lock_guard<std::mutex> lock(cout_mutex);
//cout_mutex.lock();
std::cout << __TIME__ << msg << std::endl; // __TIME__ 是编译期常量
//cout_mutex.unlock();
}
};
// 静态成员变量的定义和初始化
std::once_flag Log::ONCE; // 类内的静态成员变量只是声明,不会分配内存,必须在类外定义
Log* Log::log = nullptr; // 全局指针,指向 Log 类的唯一实例

void print_error() {
Log::GetInstance().PrintLog("error"); //链式调用,GetInstance() 返回 Log 类的引用
}

int main() {
std::thread t1(print_error);
std::thread t2(print_error);
t1.join();
t2.join();

return 0;
}

Example

C++11后多个线程初始化同一 静态局部变量 无需 call_once ,线程安全,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 简易懒汉式单例模式
class Singleton {
private:
Singleton() {}

public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

static Singleton& GetInstance() {
static Singleton instance; // ✅ C++11 起线程安全
return instance;
}
};