C++11多线程
一:创建线程
要创建线程,我们需要一个可调用的函数或函数对象,作为线程的入口点。在C++11中,我们可以使用函数指针、函数对象或lambda表达式来实现。
创建线程的基本语法如下:
#include <thread>std::thread t(function_name, args...);
function_name
是线程入口点的函数或可调用对象
args...
是传递给函数的参数
创建线程后,我们可以使用`t.join()`等待线程完成,或者使用`t.detach()`分离线程,让它在后台运行。
二:常见未定义错误
1. 传递临时变量的问题:
#include <iostream>
#include <thread>
void foo(int& x) {//引用类型
x += 1;
}
int main() {
std::thread t(foo, 1); // 传递临时变量
t.join();
return 0;
}
在这个例子中,我们定义了一个名为`foo`的函数,它接受一个整数引用作为参数,并将该引用加1。然后,我们创建了一个名为`t`的线程,将`foo`函数以及一个临时变量`1`作为参数传递给它。这样会导致在线程函数执行时,临时变量`1`被销毁,从而导致未定义行为。
解决方案是将变量复制到一个持久的对象中,然后将该对象传递给线程。例如,我们可以将`1`复制到一个`int`类型的变量中,然后将该变量的引用传递给线程。
#include <iostream>
#include <thread>
void foo(int& x) {//引用类型
x += 1;
}
int main() {
int x = 1; // 将变量复制到一个持久的对象中
std::thread t(foo, std::ref(x)); // 将变量的引用传递给线程
t.join();
return 0;
}
2.传递指针或引用指向局部变量的问题:
#include <iostream>
#include <thread>
void foo(int* ptr) {
std::cout << *ptr << std::endl; // 访问已经被销毁的指针
}
int main() {
int x = 1;
std::thread t(foo, &x); // 传递指向局部变量的指针
t.join();
return 0;
}
在这个例子中,我们定义了一个名为`foo`的函数,它接受一个整型指针作为参数,并输出该指针所指向的整数值。然后,我们创建了一个名为`t`的线程,将`foo`函数以及指向局部变量`x`的指针作为参数传递给它。这样会导致在线程函数执行时,指向局部变量`x`的指针已经被销毁,从而导致未定义行为。
解决方案是将指针或引用指向堆上的变量,或使用`std::shared_ptr`等智能指针来管理对象的生命周期。例如,我们可以使用`new`运算符在堆上分配一个整数变量,并将指针指向该变量。
#include <iostream>
#include <thread>
void foo(int* ptr) {
std::cout << *ptr << std::endl;
delete ptr; // 在使用完指针后,需要手动释放内存
}
int main() {
int* ptr = new int(1); // 在堆上分配一个整数变量
std::thread t(foo, ptr); // 将指针传递给线程
t.join();
return 0;
}
3.传递指针或引用指向已释放的内存的问题:
#include <iostream>
#include <thread>
void foo(int& x) {
std::cout << x << std::endl; // 访问已经被释放的内存
}
int main() {
int* ptr = new int(1);
std::thread t(foo, *ptr); // 传递已经释放的内存
delete ptr;
t.join();
return 0;
}
在这个例子中,我们定义了一个名为`foo`的函数,它接受一个整数引用作为参数,并输出该引用的值。然后,我们创建了一个名为`t`的线程,将`foo`函数以及一个已经被释放的指针所指向的整数值作为参数传递给它解决方案是确保在线程函数执行期间,被传递的对象的生命周期是有效的。例如,在主线程中创建并初始化对象,然后将对象的引用传递给线程。
#include <iostream>
#include <thread>
void foo(int& x) {
std::cout << x << std::endl;
}
int main() {
int x = 1;
std::thread t(foo, std::ref(x)); // 将变量的引用传递给线程
t.join();
return 0;
}
在这个例子中,我们创建了一个名为`x`的整数变量,并初始化为`1`。然后,我们创建了一个名为`t`的线程,将`foo`函数以及变量`x`的引用作为参数传递给它。这样可以确保在线程函数执行期间,变量`x`的生命周期是有效的。
4.类成员函数作为入口函数,类对象被提前释放
错误示例:
#include <iostream>
#include <thread>
class MyClass {
public:
void func() {
std::cout << "Thread " << std::this_thread::get_id()
<< " started" << std::endl;
// do some work
std::cout << "Thread " << std::this_thread::get_id()
<< " finished" << std::endl;
}
};
int main() {
MyClass obj;
std::thread t(&MyClass::func, &obj);
// obj 被提前销毁了,会导致未定义的行为
return 0;
}
上面的代码中,在创建线程之后,obj 对象立即被销毁了,这会导致在线程执行时无法访问 obj 对象,可能会导致程序崩溃或者产生未定义的行为。
为了避免这个问题,可以使用 std::shared_ptr 来管理类对象的生命周期,确保在线程执行期间对象不会被销毁。具体来说,可以在创建线程之前,将类对象的指针封装在一个 std::shared_ptr 对象中,并将其作为参数传递给线程。这样,在线程执行期间,即使类对象的所有者释放了其所有权,std::shared_ptr 仍然会保持对象的生命周期,直到线程结束。
以下是使用 std::shared_ptr 修复上面错误的示例:
#include <iostream>
#include <thread>
#include <memory>
class MyClass {
public:
void func() {
std::cout << "Thread " << std::this_thread::get_id()
<< " started" << std::endl;
// do some work
std::cout << "Thread " << std::this_thread::get_id()
<< " finished" << std::endl;
}
};
int main() {
std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();
//调用类的成员函数,需要传入类对象
std::thread t(&MyClass::func, obj);
t.join();
return 0;
}
上面的代码中,使用 std::make_shared 创建了一个 MyClass 类对象,并将其封装在一个 std::shared_ptr 对象中。然后,将 std::shared_ptr 对象作为参数传递给线程。这样,在线程执行期间,即使 obj 对象的所有者释放了其所有权,std::shared_ptr 仍然会保持对象的生命周期,直到线程结束。
5.入口函数为类的私有成员函数
#include <iostream>
#include <thread>
class MyClass {
private:
friend void myThreadFunc(MyClass* obj);
void privateFunc(){
std::cout << "Thread "
<< std::this_thread::get_id() << " privateFunc" << std::endl;
}
};
void myThreadFunc(MyClass* obj) {
obj->privateFunc();
}
int main() {
MyClass obj;
std::thread thread_1(myThreadFunc, &obj);
thread_1.join();
return 0;
}
上面的代码中,将 myThreadFunc 定义为 MyClass 类的友元函数,并在函数中调用 privateFunc 函数。在创建线程时,需要将类对象的指针作为参数传递给线程。
三.互斥量
1.概念
互斥量(mutex)是一种用于实现多线程同步的机制,用于确保多个线程之间对共享资源的访问互斥。互斥量通常用于保护共享数据的访问,以避免多个线程同时访问同一个变量或者数据结构而导致的数据竞争问题。
互斥量提供了两个基本操作:lock() 和 unlock()。当一个线程调用 lock() 函数时,如果互斥量当前没有被其他线程占用,则该线程获得该互斥量的所有权,可以对共享资源进行访问。如果互斥量当前已经被其他线程占用,则调用 lock() 函数的线程会被阻塞,直到该互斥量被释放为止。
#include <iostream>
#include <thread>
#include <mutex>
int shared_data = 0;
std::mutex mtx;
void func(int n) {
for (int i = 0; i < 100000; ++i) {
mtx.lock();
shared_data++;
std::cout << "Thread " << n
<< " increment shared_data to " << shared_data << std::endl;
mtx.unlock();
}
}
int main() {
std::thread t1(func, 1);
std::thread t2(func, 2);
t1.join();
t2.join();
std::cout << "Final shared_data = " << shared_data << std::endl;
return 0;
}
上面的代码中,定义了一个名为 shared_data 的全局变量,并使用互斥量 mtx 来确保多个线程对其进行访问时的线程安全。在两个线程中,分别调用了 func 函数,并传递了不同的参数。在 func 函数中,先获取互斥量的所有权,然后对 shared_data 变量进行累加操作,并输出变量的当前值。最后再释放互斥量的所有权。
2.死锁
假设有两个线程 T1 和 T2,它们需要对两个互斥量 mtx1 和 mtx2 进行访问,而且需要按照以下顺序获取互斥量的所有权:
- T1 先获取 mtx1 的所有权,再获取 mtx2 的所有权。
- T2 先获取 mtx2 的所有权,再获取 mtx1 的所有权。
如果两个线程同时执行,就会出现死锁问题。因为 T1 获取了 mtx1 的所有权,但是无法获取 mtx2 的所有权,而 T2 获取了 mtx2 的所有权,但是无法获取 mtx1 的所有权,两个线程互相等待对方释放互斥量,导致死锁。
四:lock_guard和unique_lock
1. lock_guard
std::lock_guard 是 C++ 标准库中的一种互斥量封装类,用于保护共享数据,防止多个线程同时访问同一资源而导致的数据竞争问题。
std::lock_guard 的特点如下:
当构造函数被调用时,该互斥量会被自动锁定。
当析构函数被调用时,该互斥量会被自动解锁。
std::lock_guard 对象不能复制或移动,因此它只能在局部作用域中使用。
2. std::unique_lock
std::unique_lock 是 C++ 标准库中提供的一个互斥量封装类,用于在多线程程序中对互斥量进行加锁和解锁操作。它的主要特点是可以对互斥量进行更加灵活的管理,包括延迟加锁、条件变量、超时等。
std::unique_lock 提供了以下几个成员函数:
lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁。
try_lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回 false,否则返回 true。
try_lock_for(const std::chrono::duration<Rep, Period>& rel_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间。
try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间点。
unlock():对互斥量进行解锁操作。
除了上述成员函数外,std::unique_lock 还提供了以下几个构造函数:
unique_lock() noexcept = default:默认构造函数,创建一个未关联任何互斥量的 std::unique_lock 对象。
explicit unique_lock(mutex_type& m):构造函数,使用给定的互斥量 m 进行初始化,并对该互斥量进行加锁操作。
unique_lock(mutex_type& m, defer_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,但不对该互斥量进行加锁操作。
unique_lock(mutex_type& m, try_to_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,并尝试对该互斥量进行加锁操作。如果加锁失败,则创建的 std::unique_lock 对象不与任何互斥量关联。
unique_lock(mutex_type& m, adopt_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,并假设该互斥量已经被当前线程成功加锁。
五:std::call_once与其使用场景
单例设计模式是一种常见的设计模式,用于确保某个类只能创建一个实例。由于单例实例是全局唯一的,因此在多线程环境中使用单例模式时,需要考虑线程安全的问题。
下面是一个简单的单例模式的实现:
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; //饿汉模式
return instance;
}
void setData(int data) {
m_data = data;
}
int getData() const {
return m_data;
}
private:
Singleton() {}//单例模式构造函数为private
Singleton(const Singleton&) = delete; //禁止拷贝构造
Singleton& operator=(const Singleton&) = delete; //禁止=
int m_data = 0;
};
在这个单例类中,我们使用了一个静态成员函数 getInstance() 来获取单例实例,该函数使用了一个静态局部变量 instance 来存储单例实例。由于静态局部变量只会被初始化一次,因此该实现可以确保单例实例只会被创建一次。
但是,该实现并不是线程安全的。如果多个线程同时调用 getInstance() 函数,可能会导致多个对象被创建,从而违反了单例模式的要求。此外,如果多个线程同时调用 setData() 函数来修改单例对象的数据成员 m_data,可能会导致数据不一致或不正确的结果。
为了解决这些问题,我们可以使用 std::call_once 来实现一次性初始化,从而确保单例实例只会被创建一次。下面是一个使用 std::call_once 的单例实现:
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(m_onceFlag, &Singleton::init);//避免了多个对象被创建
return *m_instance;
}
void setData(int data) {
m_data = data;
}
int getData() const {
return m_data;
}
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static void init() {
m_instance.reset(new Singleton);
}
static std::unique_ptr<Singleton> m_instance; //避免了内存泄漏
static std::once_flag m_onceFlag;
int m_data = 0;
};
std::unique_ptr<Singleton> Singleton::m_instance;
std::once_flag Singleton::m_onceFlag;
在这个实现中,我们使用了一个静态成员变量 m_instance 来存储单例实例,使用了一个静态成员变量 m_onceFlag 来标记初始化是否已经完成。在 getInstance() 函数中,我们使用 std::call_once 来调用 init() 函数,确保单例实例只会被创建一次。在 init() 函数中,我们使用了 std::unique_ptr 来创建单例实例。
使用 std::call_once 可以确保单例实例只会被创建一次,从而避免了多个对象被创建的问题。此外,使用 std::unique_ptr 可以确保单例实例被正确地释放,避免了内存泄漏的问题。
六:condition_variable 与其使用场景
std::condition_variable 的步骤如下:
创建一个 std::condition_variable 对象。
创建一个互斥锁 std::mutex 对象,用来保护共享资源的访问。
在需要等待条件变量的地方,使用 std::unique_lock<std::mutex> 对象锁定互斥锁,并调用 std::condition_variable::wait()、std::condition_variable::wait_for() 或 std::condition_variable::wait_until() 函数等待条件变量。
在其他线程中需要通知等待的线程时,调用 std::condition_variable::notify_one() 或 std::condition_variable::notify_all() 函数通知等待的线程。
生产者与消费者模型
下面是一个简单的生产者-消费者模型的案例,其中使用了 std::condition_variable
来实现线程的等待和通知机制:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex g_mutex;
std::condition_variable g_cv;
std::queue<int> g_queue;
void Producer() {
for (int i = 0; i < 10; i++) {
{
std::unique_lock<std::mutex> lock(g_mutex);
g_queue.push(i);
std::cout << "Producer: produced " << i << std::endl;
}
g_cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void Consumer() {
while (true) {
std::unique_lock<std::mutex> lock(g_mutex);
g_cv.wait(lock, []() { return !g_queue.empty(); });
int value = g_queue.front();
g_queue.pop();
std::cout << "Consumer: consumed " << value << std::endl;
}
}
int main() {
std::thread producer_thread(Producer);
std::thread consumer_thread(Consumer);
producer_thread.join();
consumer_thread.join();
return 0;
}
使用 std::condition_variable
可以实现线程的等待和通知机制,从而在多线程环境中实现同步操作。在生产者-消费者模型中,使用 std::condition_variable
可以让消费者线程等待生产者线程生产数据后再进行消费,避免了数据丢失或者数据不一致的问题。
七:跨平台线程池
它使用 C++11 标准库中的 std::thread、std::mutex、std::condition_variable、std::function 和 std::queue 等组件实现。
#include<iostream>
#include<thread>
#include<mutex>
#include<queue>
#include<condition_variable>
#include<string>
#include<vector>
#include<functional>
class ThreadPool {
public:
ThreadPool(int numThreads) : stop(false){
for (int i = 0; i < numThreads; i++) {
threads.emplace_back([this] { //[this]是一个捕获列表,它使得lambda表达式可以访问ThreadPool对象的成员 //线程不支持拷贝
while (1) { //while (1) { ... }:这是一个无限循环,每个线程都会在这个循环中等待和执行任务。
std::unique_lock<std::mutex> lock(mtx); //创建一个互斥锁(mutex)的锁对象lock,用于保护对共享资源(如任务队列tasks)的访问。
//线程在这里等待,直到满足某个条件。条件是任务队列tasks不为空,或者stop为true。
//condition.wait会释放锁lock,允许其他线程访问共享资源,并在条件满足时重新获取锁。
condition.wait(lock, [this] { //判断队列里是否有任务
return !tasks.empty() || stop;
});
//如果stop为true且任务队列tasks为空,线程会退出循环并结束。
if (stop && tasks.empty()) {
return;
}
//从任务队列tasks中获取第一个任务(类型为std::function<void()>),并将其从队列中移除。
//这里使用了std::move来将左值转换为右值,以便进行移动语义的操作。
std::function<void()> task(std::move(tasks.front()));// front首元素的引用,move 左值改为右值
tasks.pop();
lock.unlock();
//执行任务
task();
}
});
}
}
~ThreadPool() {
{ //限定锁的作用域
std::unique_lock<std::mutex> lock(mtx);
stop = true;
}
//condition是一个条件变量,用于线程同步。
//notify_all方法会唤醒所有等待在condition上的线程。这通常用于通知线程池中的工作线程停止执行,因为stop变量已经被设置为true。
condition.notify_all();
/*这个循环遍历threads容器(可能是一个线程对象的集合)。对于容器中的每个线程对象t,调用join方法。
join方法会阻塞当前线程,直到被调用的线程完成执行。这确保了线程池中的所有线程都完成了它们的工作,并且它们的资源已经被正确释放。*/
for (auto& t : threads) {
t.join();
}
}
template<class F , class ...Args> //可变参数模板,多参数
void enqueue(F&& f, Args &&... args) { //增加任务
std::function<void()>task =
std::bind(std::forward<F>(f), std::forward<Args>(args)...);//完美转发
{
std::unique_lock<std::mutex> lock(mtx);
tasks.emplace(std::move(task));
}
condition.notify_all();
}
private:
std::vector<std::thread> threads; //线程数组
std::queue<std::function<void()>> tasks; //任务队列
/*std::function是一个可调用的对象包装器,通过类进行实现,在编码时可以通过指定模板参数使用统一的方式处理函数、
函数指针并且可以当做回调函数进行使用。在实际编程时,主要有以下场景:
绑定一个函数(普通函数或者静态函数)
实现回调函数
作为函数入参*/
std::mutex mtx; //任务队列互斥锁
std::condition_variable condition; //任务队列条件变量,通知线程有新任务可执行
bool stop; //线程什么时候终止
};
int main() {
ThreadPool pool(4);//线程数量
for (int i = 0; i < 10; i++) {
pool.enqueue([i]{
std::cout << "task: " << i <<" is running"<< std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "task: " << i << " is done" << std::endl;
});
}
return 0;
}
在这个示例中,我们同样定义了一个 ThreadPool 类,并且在构造函数中创建了指定数目的线程。在每个线程中,我们不断地从任务队列中获取任务并执行,直到线程池被停止。在 enqueue() 函数中,我们将任务封装成一个 std::function 对象,并将它添加到任务队列中。在 ThreadPool 的析构函数中,我们等待所有线程执行完成后再停止所有线程。
在主函数中,我们创建了一个 ThreadPool 对象,并向任务队列中添加了 8 个任务。每个任务会输出一些信息,并且在执行完后等待 1 秒钟。由于线程池中有 4 个线程,因此这 8 个任务会被分配到不同的线程中执行。在任务执行完成后,程序会退出。
八:异步并发—— async future packaged_task promise
1. async 、 future
是C++11引入的一个函数模板,用于异步执行一个函数,并返回一个std::future对象,表示异步操作的结果。使用std::async可以方便地进行异步编程,避免了手动创建线程和管理线程的麻烦。下面是一个使用std::async的案例:
#include <iostream>
#include <future>
int calculate() {
// 模拟一个耗时的计算
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
}
int main() {
std::future<int> future_result
= std::async(std::launch::async, calculate);
// 在这里可以做其他的事情
int result = future_result.get(); // 获取异步操作的结果
std::cout << result << std::endl; // 输出42
return 0;
}
这个例子中,我们使用std::async函数异步执行了一个耗时的计算,这个计算可以在另一个线程中执行,不会阻塞主线程。同时,我们也避免了手动创建线程和管理线程的麻烦。
2. packaged_task
在C++中,packaged_task是一个类模板,用于将一个可调用对象(如函数、函数对象或Lambda表达式)封装成一个异步操作,并返回一个std::future对象,表示异步操作的结果。packaged_task可以方便地将一个函数或可调用对象转换成一个异步操作,供其他线程使用。
以下是packaged_task的基本用法:
2.1. 定义可调用对象
int calculate(int x, int y) {
return x + y;
}
这里定义了一个函数calculate,用于将两个整数相加。
2.2. 创建packaged_task对象
std::packaged_task<int(int, int)> task(calculate);
std::future<int> future_result = task.get_future();
这里创建了一个packaged_task对象,将函数calculate封装成异步操作,并返回一个std::future对象,表示异步操作的结果。
2.3. 在其他线程中执行异步操作
std::thread t(std::move(task), 1, 2);
t.join();
这里创建了一个新的线程,并在这个线程中执行异步操作。由于packaged_task对象是可移动的,因此需要使用std::move()函数将task对象转移至新线程中执行。
2.4. 获取异步操作的结果
int result = future_result.get();
std::cout << result << std::endl; // 输出3
在主线程中,我们可以使用future_result.get()方法获取异步操作的结果,并输出到控制台。
在这个例子中,我们成功地将一个函数calculate封装成了一个异步操作,并在其他线程中执行。通过packaged_task和future对象,我们可以方便地实现异步编程,使得代码更加简洁和易于维护。
3. promise
在C++中,promise是一个类模板,用于在一个线程中产生一个值,并在另一个线程中获取这个值。promise通常与future和async一起使用,用于实现异步编程。
以下是promise的基本用法:
3.1. 创建promise对象
std::promise p;
这里创建了一个promise对象,用于产生一个整数值。
3.2. 获取future对象
std::future f = p.get_future();
通过promise对象的get_future()方法,可以获取与之关联的future对象,用于在另一个线程中获取promise对象产生的值。
3.3. 在其他线程中设置值
std::thread t(&p {
p.set_value(42);
});
t.join();
这里创建了一个新的线程,并在这个线程中,使用promise对象的set_value()方法设置一个整数值42。
3.4. 在主线程中获取值
int result = f.get();
std::cout << result << std::endl; // 输出42
在主线程中,我们可以使用future对象的get()方法获取promise对象产生的值,并输出到控制台。
在这个例子中,我们成功地使用promise和future对象实现了跨线程的值传递。通过promise和future对象,我们可以方便地实现异步编程,避免了手动创建线程和管理线程的麻烦。
九.原子操作 atomic
std::atomic
是 C++11 标准库中的一个模板类,用于实现多线程环境下的原子操作。它提供了一种线程安全的方式来访问和修改共享变量,可以避免多线程环境中的数据竞争问题。
std::atomic
的使用方式类似于普通的 C++ 变量,但是它的操作是原子性的。也就是说,在多线程环境下,多个线程同时对同一个 std::atomic
变量进行操作时,不会出现数据竞争问题。
以下是一些常用的 std::atomic
操作:
load()
:将std::atomic
变量的值加载到当前线程的本地缓存中,并返回这个值。store(val)
:将val
的值存储到std::atomic
变量中,并保证这个操作是原子性的。exchange(val)
:将val
的值存储到std::atomic
变量中,并返回原先的值。compare_exchange_weak(expected, val)
和compare_exchange_strong(expected, val)
:比较std::atomic
变量的值和expected
的值是否相同,如果相同,则将val
的值存储到std::atomic
变量中,并返回true
;否则,将std::atomic
变量的值存储到expected
中,并返回false
。
以下是一个示例,演示了如何使用 std::atomic
进行原子操作:
#include <atomic>
#include <iostream>
#include <thread>
std::atomic<int> count = 0; //原子操作
void increment() {
for (int i = 0; i < 1000000; ++i) {
count++;
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << count << std::endl;
return 0;
}
在这个示例中,我们定义了一个 std::atomic<int>
类型的变量 count
,并将其初始化为 0。然后,我们启动两个线程分别执行 increment
函数,这个函数的作用是将 count
变量的值加一,执行一百万次。最后,我们在主线程中输出 count
变量的值。由于 count
变量是一个 std::atomic
类型的变量,因此对它进行操作是原子性的,不会出现数据竞争问题。在这个示例中,最终输出的 count
变量的值应该是 2000000