一,线程创建
1,创建线程
可以通过std::thread (线程名,参数)参数可以有多个 快速创建线程(c++11新特性)。
2,std::thread::join()
被调用时,当前线程会被阻塞,直到被调用的线程完成其执行。一旦被调用的线程完成,std::thread::join()
函数会返回,当前线程可以继续执行。
bool joinable()可以返回一个线程是否可以执行join();
#include<iostream>
#include<thread>
#include<string>
using namespace std;
void thread_1(string a,string b)
{
std::cout << a << std::endl;
// 模拟一些耗时操作
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << b << std::endl;
}
int main()
{
string a = "Thread is running.";
string b = "Thread has finished.";
std::thread thread1(thread_1,a,b);
bool isjoin = thread1.joinable();
if (isjoin)
{
thread1.join();
}
//当join线程未执行完时程序将被阻塞在此
return 0;
}
3,std::thread::detach()
std::thread::detach()
被调用时,当前线程会与被调用的线程分离,两者可以独立执行。一旦调用了 std::thread::detach()
,就无法再对该线程调用 join()
或其他线程管理函数。
需要注意的是,一旦调用了 std::thread::detach()
,就需要确保被分离的线程能够独立执行,并且不依赖于主线程的状态。否则可能会导致未定义行为或资源泄漏。
在多线程编程中,使用 std::thread::detach()
可以将某个线程与主线程分离,使得它们可以并发执行。但需要注意管理好线程的生命周期和资源释放,以避免潜在的问题。
4,等待函数std::this_thread::sleep_for()
std::this_thread::sleep_for(std::chrono::seconds(3));线程等待函数
5,将一个对象转换为对应的引用类型。std::ref()
std::ref()
是 C++ 标准库中的一个函数模板,位于 <functional>
头文件中。它用于将一个对象转换为对应的引用类型。
在多线程编程中,当需要传递一个对象给线程函数,并希望通过引用来操作该对象时,可以使用 std::ref()
来将该对象包装成引用类型。
#include <iostream>
#include <functional>
void increment(int& num) {
++num;
}
int main() {
int num = 0;
// 使用 std::ref() 将 num 包装成引用类型
std::thread thread1(increment, std::ref(num));
// 等待线程结束
thread1.join();
std::cout << "num: " << num << std::endl; // 输出:num: 1
return 0;
}
6,参数使用局部变量进行引用报错
#include <iostream>
#include <thread>
#include <string>
using namespace std;
thread t;
void thread_1(std::string& a) {
cout << a << endl;
}
void ami()
{
std::string a = "Thread is running.";
t=thread(thread_1, ref(a));
}
//当函数结束时a的地址将被释放
int main() {
ami();
t.join();
return 0;
}
7,数据互斥
当多个线程同时对一个变量进行操作,系那个有可能产生错误的结果,例如:a=4,两个线程同时对a进行加1操作,那么a的值可能等于5,从而产生了错误的结果。因此音高设计成当一个线程访问一个变量时不允许其他线程访问。
#include <iostream>
#include <thread>
#include <string>
using namespace std;
int a = 0;
void thread_1() {
for (int i = 0; i < 10000; i++)
{
a++;
}
}
void thread_2() {
for (int i = 0; i < 10000; i++)
{
a++;
}
}
int main() {
thread t(thread_1);
thread t2(thread_2);
t.join();
t2.join();
cout << a;
return 0;
}
//可以通过以下方法加入互斥锁解决
std::mutex m;
void thread_1() {
for (int i = 0; i < 10000; i++)
{
m.lock();
a++;
m.unlock();
}
}
//要注意预防死锁
lock_guard模板类可以自动进行解锁,构造函数加锁,析构函数解锁
std::mutex m;
void thread_1() {
for (int i = 0; i < 10000; i++)
{
lock_guard<mutex> b(m);
a++;
}
}
还有unique_lock模板类可以进行更多的操作,但相对的占用资源也就更多。
操作更加灵活,在lock_guard的基础上还具有延时枷锁,超时等操作。
8.move()
std::move()
是 C++ 中的一个函数模板,用于将对象的所有权从一个对象转移到另一个对象,并保证转移后原始对象处于有效但未定义的状态。
在 C++11 中引入了右值引用(Rvalue Reference)的概念,通过使用右值引用,我们可以实现高效的资源转移和移动语义。std::move()
就是利用右值引用来实现对象的转移操作。
具体来说,std::move()
接受一个对象作为参数,并返回对该对象的右值引用。这意味着它将传递的对象标记为右值,表示可以对其进行资源的转移而不是拷贝。
使用 std::move()
可以帮助我们实现以下几个目的:
- 转移对象的所有权:通过将对象标记为右值,可以将其所有权转移到另一个对象,避免了昂贵的拷贝操作。
- 启用移动语义:对于支持移动语义的类型,可以使用
std::move()
来调用移动构造函数或移动赋值运算符,从而提高性能。 - 显示标识对象状态的改变:通过将对象标记为右值,明确表示该对象可能会被修改或转移所有权,增加代码的可读性和可维护性。
示例代码
#include <iostream>
#include <vector>
int main() {
std::vector<int> source = {1, 2, 3, 4, 5};
// 使用 std::move() 将 source 的所有权转移到 target
std::vector<int> target = std::move(source);
// 此时 source 处于有效但未定义的状态
std::cout << "Size of source: " << source.size() << std::endl; // 输出0
std::cout << "Size of target: " << target.size() << std::endl; // 输出5
return 0;
}
在这个示例中,通过调用 std::move(source)
,我们将 source 的所有权转移到了 target。此时,source 虽然仍然是有效的,但其内容已经被移动到了 target 中。
需要注意的是,使用 std::move()
并不意味着对象的内容会发生任何变化,它只是将所有权从一个对象转移到另一个对象。因此,在转移后的对象上可以继续进行修改或访问操作。
使用 std::move()
时要小心,确保对象在转移后不再被使用,以避免悬空指针或使用无效对象的错误。同时,只有对支持移动语义的类型(如标准库容器、智能指针等)才能实现高效的转移操作。对于内置类型或没有实现移动语义的类型,std::move()
仍然会退化为拷贝操作。
二,单例模式
1,懒汉模式
单例模式是一种设计模式,用于确保一个类只有一个实例,并提供全局访问点以获取该实例。它的主要目的是限制某个类只能创建一个对象,从而节省系统资源,同时提供对该对象的方便访问。
实现方法:
以下是懒汉模式
1,将类的构造方法定义为私有方法
2,定义一个私有的类的静态实例
3,提供一个公有的获取实例的静态方法
单例模式通常在以下情况下使用:
- 当一个类只需要一个实例来协调操作时,例如线程池、缓存、日志记录器等。
- 当创建对象时的开销较大,但只需要一个实例来提供全局访问时,可以避免频繁创建和销毁对象。
- 当多个实例可能导致不一致性或冲突时,例如配置文件管理器、数据库连接池等。
实现单例模式的关键是确保类只能有一个实例,并提供一个访问该实例的全局入口
#include<iostream>
using namespace std;
std::mutex m;
class Singleton {
private:
static Singleton* instance; // 静态成员变量,保存唯一实例的指针
Singleton() {} // 私有构造函数,防止外部创建对象
public:
static Singleton* getInstance() {
if (instance == nullptr) {
m.lock();
if (instance == nullptr) {
instance = new Singleton(); // 只在第一次调用时创建实例
注意:此处是线程不安全的,如果两个线程同时运行到此处,将有可能创建出两个实例,可以通过判断两次是否为空和加锁来解决,如红字所示
}
m.unlock();
}
return instance;
}
void doSomething() {
// 单例对象的其他方法
cout << "do something" << endl;
}
};
// 初始化静态成员变量
Singleton* Singleton::instance = nullptr;
int main() {
Singleton* s1 = Singleton::getInstance(); // 获取单例对象
s1->doSomething();
Singleton* s2 = Singleton::getInstance(); // 再次获取,仍然是同一个实例
s2->doSomething();
return 0;
}
2,饿汉模式
直接将instance在使用前就实例化而饿汉模式是在使用的时候才进行实例化。
主要区别就在于实例化对象的时机上。
区别就在于懒汉模式没有if判断以及直接将instance实例化
Singleton* Singleton::instance = new Singleton ;
if (instance == nullptr) {
instance = new Singleton(); // 只在第一次调用时创建实例
}
3,模板类
template <typename T>
class Singleton {
public:
static T& getInstance() {
static T instance; // 在首次调用时进行实例化,确保只有一个实例
return instance;
}
// 删除拷贝构造函数和赋值运算符重载,避免复制实例
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
protected:
Singleton() {} // 构造函数声明为受保护,防止外部实例化
};
在上面的示例中,我们定义了一个模板类 Singleton
,通过静态成员函数 getInstance
返回单例对象的引用。使用 static
局部变量确保只有一个实例,同时删除拷贝构造函数和赋值运算符重载,以防止复制实例。
使用该模板类时,只需将具体的类作为模板参数传入即可:
class MyClass {
public:
void print() {
cout << "This is MyClass" << endl;
}
};
int main() {
MyClass& obj1 = Singleton<MyClass>::getInstance();
MyClass& obj2 = Singleton<MyClass>::getInstance();
obj1.print(); // 输出:This is MyClass
obj2.print(); // 输出:This is MyClass
// 编译错误:拷贝构造函数被删除
// MyClass obj3 = obj1;
return 0;
}
4,使用call_once()设计懒汉模式
std::call_once()
是 C++11 标准库中提供的一个函数,用于在多线程环境下只执行一次指定的函数。它通常与 std::once_flag
结合使用,确保多线程环境下的初始化操作只会执行一次。
template< class Callable, class... Args > void call_once( std::once_flag& flag, Callable&& f, Args&&... args );
使用 std::call_once
来调用初始化函数,确保只会执行一次。想要一个函数只执行一次,需要将高函数的调用放在call_once的函数体里面,也就是说用另外一个函数来调用call_once并将想要只执行一次的函数放在call_once的函数体中。
flag
是一个std::once_flag
对象,用于标记一次性任务是否已经执行。f
是需要执行的函数或可调用对象。args
是传递给函数f
的参数。
#include <iostream>
#include <thread>
#include <mutex>
std::once_flag flag;
void initialization_function() {
std::cout << "Initialization function called with parameter " << std::endl;
}
void call_once_initialization() {
std::call_once(flag, initialization_function);
}
int main() {
std::call_once(flag, initialization_function); // 使用 call_once 来执行初始化函数
std::thread t1(call_once_initialization); // 传递 call_once_initialization 函数
std::thread t2(call_once_initialization);
t1.join();
t2.join();
return 0;
}
该代码将只打印一次
使用call_once设计的懒汉模式
#include <iostream>
#include <mutex>
class LazySingleton {
public:
static LazySingleton& getInstance() {
std::call_once(flag, createInstance);
return *instance;
}
void printMessage() {
std::cout << "LazySingleton instance" << std::endl;
}
private:
LazySingleton() = default; // 私有化构造函数
// 创建实例的函数
static void createInstance() {
instance = new LazySingleton();
}
static LazySingleton* instance; // 单例对象指针
static std::once_flag flag; // 标记是否已创建实例
};
LazySingleton* LazySingleton::instance = nullptr;
std::once_flag LazySingleton::flag;
int main() {
LazySingleton& obj1 = LazySingleton::getInstance();
LazySingleton& obj2 = LazySingleton::getInstance();
obj1.printMessage(); // 输出:LazySingleton instance
obj2.printMessage(); // 输出:LazySingleton instance
return 0;
}
5,条件变量
条件变量(Condition Variable)是一种同步机制,用于在线程之间进行通信和同步。它是基于线程的等待和通知机制,允许线程在某个条件满足或者发生变化时等待或者被唤醒。
条件变量通常与互斥量(Mutex)一起使用,以实现更复杂的线程同步和线程间通信。互斥量用于保护共享资源,而条件变量则用于在特定条件下阻塞或唤醒线程。
C++ 标准库中提供了 std::condition_variable
类来实现条件变量。其基本的使用包括以下几个关键方法:
wait()
: 当线程遇到条件不满足时,调用该方法会阻塞当前线程,直到其他线程通过notify_one()
或notify_all()
唤醒了等待的线程。notify_one()
: 用于唤醒一个等待中的线程,如果有多个线程在等待,则只会唤醒其中一个。notify_all()
: 用于唤醒所有等待中的线程。
典型的使用场景是,一个线程等待某个条件为真,而其他线程在某个条件满足时通知等待的线程继续执行。在等待前,等待线程需要先获取互斥量的锁,以避免竞态条件。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker_thread() {
std::unique_lock<std::mutex> lock(mtx); // 获取互斥量的锁
// 等待条件满足
cv.wait(lock, []{ return ready; });
// 执行工作
std::cout << "Worker thread is executing..." << std::endl;
}
int main() {
std::thread worker(worker_thread);
// 模拟一段耗时的操作
std::this_thread::sleep_for(std::chrono::seconds(2));
{
std::lock_guard<std::mutex> lock(mtx); // 获取互斥量的锁
// 设置条件为真
ready = true;
}
// 通知等待的线程继续执行
cv.notify_one();
worker.join();
return 0;
}
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cond;
std::queue<int> buffer;
const int MAX_SIZE = 5;
void producer()
{
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
while (buffer.size() == MAX_SIZE) { // 缓冲区已满,等待消费者取出数据
std::cout << "Buffer is full, wait for consumer...\n";
cond.wait(lock);
}
buffer.push(i);
std::cout << "Produced: " << i << "\n";
lock.unlock();
cond.notify_one(); // 通知消费者可以取出数据
}
}
void consumer()
{
while (true) {
std::unique_lock<std::mutex> lock(mtx);
while (buffer.empty()) { // 缓冲区为空,等待生产者插入数据
std::cout << "Buffer is empty, wait for producer...\n";
cond.wait(lock);
}
int data = buffer.front();
buffer.pop();
std::cout << "Consumed: " << data << "\n";
lock.unlock();
cond.notify_one(); // 通知生产者可以插入数据
}
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
三,线程池
1,创建任务队列
template<typename F, typename... 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(mutex); // 创建独占互斥量的锁
tasks.emplace(std::move(task)); // 将任务移动到任务队列中
}
condition.notify_one(); // 唤醒一个等待条件变量的线程
}
这段代码是线程池中的一个函数,名为 enqueue
。它的作用是将一个任务添加到线程池中,并唤醒一个等待条件变量的线程来执行该任务。
该函数使用了可变参数模板(variadic templates)和完美转发(perfect forwarding)的特性,可以接受任意类型的可调用对象和参数,并将它们绑定到函数对象中。
具体来说,该函数的参数列表包含一个可调用对象类型的参数 f
,以及一组可变参数包 args
。在函数内部,我们首先利用 std::forward
将 f
和 args
转发为右值引用或左值引用,然后使用 std::bind
将它们绑定到一个新的函数对象中。注意,由于 std::bind
返回的是一个可调用对象,而不是一个函数指针,因此我们需要将其封装在一个 std::function<void()>
类型的对象中,以便在稍后执行任务时进行调用。
接下来,在添加任务之前,我们使用一个独占互斥量的锁来保护任务队列的并发访问。然后,我们将任务对象 task
移动到任务队列 tasks
中。由于任务是临时对象,没有必要保留其值,因此我们可以使用 std::move
将其转换为右值引用并移动到队列中,避免不必要的数据拷贝和内存分配。
最后,我们使用 condition.notify_one()
唤醒一个等待条件变量 condition
的线程,使其从任务队列中取出一个任务并执行。这样,线程池就能够异步地执行添加的任务,并且可以自动调节线程的数量以适应负载的变化。
2,设计构造函数
ThreadPool(int numThreads) : stop(false) { // 线程池构造函数,初始化成员变量
for (int i = 0; i < numThreads; ++i) { // 循环创建指定数量的线程
threads.emplace_back([this] { // 向线程数组中添加线程,使用Lambda表达式作为线程函数
while (true) { // 无限循环
std::unique_lock<std::mutex> lock(mutex); // 创建独占互斥量的锁
condition.wait(lock, [this] { return stop || !tasks.empty(); }); // 等待条件变量满足
//作用是让工作线程在任务队列不为空或者线程池被要求停止运行时立即被唤醒,以执行相应的操作。
/*首先,它会对 lock 所管理的互斥量进行解锁。
然后,它将线程置于阻塞状态,直到条件变量 condition 被通知,并且 pred 返回 true。
当被通知时,它会重新锁定互斥量,并且重新评估 pred。
如果 pred 返回 true,则线程将从 wait 函数返回并继续执行;否则,线程将再次被阻塞。*/
if (stop && tasks.empty()) { // 如果线程池停止并且任务队列为空
return; // 退出线程函数
}
std::function<void()> task(std::move(tasks.front())); // 获取任务队列的首个任务
tasks.pop(); // 弹出队列中的首个任务
lock.unlock(); // 解锁
task(); // 执行任务
}
});
}
}
3,完整样例
#include <iostream> // 包含输入输出流库
#include <thread> // 包含多线程支持库
#include <mutex> // 包含互斥量库
#include <condition_variable> // 包含条件变量库
#include <functional> // 包含函数对象库
#include <queue> // 包含队列库
class ThreadPool {
public:
ThreadPool(int numThreads) : stop(false) { // 线程池构造函数,初始化成员变量
for (int i = 0; i < numThreads; ++i) { // 循环创建指定数量的线程
threads.emplace_back([this] { // 向线程数组中添加线程,使用Lambda表达式作为线程函数
while (true) { // 无限循环
std::unique_lock<std::mutex> lock(mutex); // 创建独占互斥量的锁
condition.wait(lock, [this] { return stop || !tasks.empty(); }); // 等待条件变量满足
//作用是让工作线程在任务队列不为空或者线程池被要求停止运行时立即被唤醒,以执行相应的操作。
/*首先,它会对 lock 所管理的互斥量进行解锁。
然后,它将线程置于阻塞状态,直到条件变量 condition 被通知,并且 pred 返回 true。
当被通知时,它会重新锁定互斥量,并且重新评估 pred。
如果 pred 返回 true,则线程将从 wait 函数返回并继续执行;否则,线程将再次被阻塞。*/
if (stop && tasks.empty()) { // 如果线程池停止并且任务队列为空
return; // 退出线程函数
}
std::function<void()> task(std::move(tasks.front())); // 获取任务队列的首个任务
tasks.pop(); // 弹出队列中的首个任务
lock.unlock(); // 解锁
task(); // 执行任务
}
});
}
}
~ThreadPool() { // 线程池析构函数
{
std::unique_lock<std::mutex> lock(mutex); // 创建独占互斥量的锁
stop = true; // 设置停止标志为真
}
condition.notify_all(); // 唤醒所有等待条件变量的线程
for (std::thread& thread : threads) { // 遍历线程数组
thread.join(); // 等待线程结束
}
}
template<typename F, typename... 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(mutex); // 创建独占互斥量的锁
tasks.emplace(std::move(task)); // 将任务移动到任务队列中
}
condition.notify_one(); // 唤醒一个等待条件变量的线程
}
private:
std::vector<std::thread> threads; // 线程数组
std::queue<std::function<void()>> tasks; // 任务队列
std::mutex mutex; // 互斥量
std::condition_variable condition; // 条件变量
bool stop; // 停止标志
};
int main() {
ThreadPool pool(4); // 创建包含4个线程的线程池
for (int i = 0; i < 8; ++i) { // 循环添加8个任务到线程池
pool.enqueue([i] { // 向线程池添加Lambda表达式作为任务
std::cout << "Task " << i << " is running in thread " << std::this_thread::get_id() << std::endl; // 输出任务正在运行的信息
std::this_thread::sleep_for(std::chrono::seconds(1)); // 使当前线程休眠1秒
std::cout << "Task " << i << " is done" << std::endl; // 输出任务完成的信息
});
}
return 0; // 程序正常结束
}
4,总结
1,首先需要包含相关的库文件
#include <mutex> // 包含互斥量库
#include <condition_variable> // 包含条件变量库
#include <functional> // 包含函数对象库
#include <queue> // 包含队列库
2,设计一个线程池的类
其中包括重写的构造函数与析构函数
一个用于添加任务的模板函数
3,构造函数中需要创建指定数量的线程
并且在任务队列不为空时,将该任务交给一个线程执行,
并将该任务从任务队列中弹出,进入下一次循环。
其中需要多次加锁
线程是由thread.emplace_back()中的进入lambda表达式时开启的
当没有任务时或或者是只有一个任务时,只会开启一个线程
4,析构函数中先将停止的标识符置为true,如此便可以将构造函数中的循环关闭
然后唤醒所有的线程,使他们检测到stop已经为true,线程函数返回,线程结束
5,在使用线程池时需要将任务函数传进任务队列中。
四,异步并发
1. async 、 future
是C++11引入的一个函数模板,用于异步执行一个函数,并返回一个std::future对象,表示异步操作的结果。使用std::async可以方便地进行异步编程,避免了手动创建线程和管理线程的麻烦。
std::future
std::future
是用来获取异步操作结果的对象。当你启动一个异步操作时,可以通过 std::future
来获取该操作的结果或者等待该操作的完成。你可以通过调用 get()
方法来获取异步操作的结果,或者调用 wait()
、wait_for()
、wait_until()
方法来等待异步操作的完成。
std::async
std::async
可以用来启动一个异步操作,并返回一个与之关联的 std::future
对象,用于获取该异步操作的结果。你可以指定不同的启动策略(std::launch::async
或 std::launch::deferred
),来决定异步操作是立即执行还是延迟执行。
使用 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);
//此处相当于自动为calculate创建了一个线程函数,不会阻塞程序
// 在这里可以做其他的事情
int result = future_result.get(); // 获取异步操作的结果
//.get()函数会阻塞程序直到异步操作执行完成
std::cout << result << std::endl; // 输出42
return 0;
}
2. packaged_task
在C++中,packaged_task是一个类模板,用于将一个可调用对象(如函数、函数对象或Lambda表达式)封装成一个异步操作,并返回一个std::future对象,表示异步操作的结果。packaged_task可以方便地将一个函数或可调用对象转换成一个异步操作,供其他线程使用。
以下是packaged_task的基本用法:
1. 定义可调用对象
int calculate(int x, int y) { return x + y; }
这里定义了一个函数calculate,用于将两个整数相加。
2. 创建packaged_task对象
std::packaged_task<int(int, int)> task(calculate);
std::future<int> future_result = task.get_future();
这里创建了一个packaged_task对象,将函数calculate封装成异步操作,并返回一个std::future对象,表示异步操作的结果。
3. 在其他线程中执行异步操作
std::thread t(std::move(task), 1, 2);
t.join();
这里创建了一个新的线程,并在这个线程中执行异步操作。由于packaged_task对象是可移动的,因此需要使用std::move()函数将task对象转移至新线程中执行。
4. 获取异步操作的结果
int result = future_result.get();
std::cout << result << std::endl; // 输出3
在主线程中,我们可以使用future_result.get()方法获取异步操作的结果,并输出到控制台。
在这个例子中,我们成功地将一个函数calculate封装成了一个异步操作,并在其他线程中执行。通过packaged_task和future对象,我们可以方便地实现异步编程,使得代码更加简洁和易于维护。
#include <iostream>
#include <future>
int calculate() {
// 模拟一个耗时的计算
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
}
int main() {
std::packaged_task<int()> task(calculate);
//注意此处并没有开启线程,只是将函数加入到task
auto future_result = task.get_future();
std::thread t1(std::move(task));//开启线程
std::cout << calculate() << std::endl;
t1.join();
std::cout << future_result.get() << std::endl; // 输出42
return 0;
}
3. promise
在C++中,promise是一个类模板,用于在一个线程中产生一个值,并在另一个线程中获取这个值。promise通常与future和async一起使用,用于实现异步编程。
以下是promise的基本用法:
1. 创建promise对象
std::promise<int> p;
这里创建了一个promise对象,用于产生一个整数值。
2. 获取future对象
std::future<int> f = p.get_future();
通过promise对象的get_future()方法,可以获取与之关联的future对象,用于在另一个线程中获取promise对象产生的值。
3. 在其他线程中设置值
std::thread t([&p]() { p.set_value(42); });
t.join();
这里创建了一个新的线程,并在这个线程中,使用promise对象的set_value()方法设置一个整数值42。
4. 在主线程中获取值
int result = f.get();
std::cout << result << std::endl; // 输出42
在主线程中,我们可以使用future对象的get()方法获取promise对象产生的值,并输出到控制台。
在这个例子中,我们成功地使用promise和future对象实现了跨线程的值传递。通过promise和future对象,我们可以方便地实现异步编程,避免了手动创建线程和管理线程的麻烦。
#include <iostream>
#include <future>
void func(std::promise<int>& f)
{
f.set_value(1000);
}
int main()
{
std::promise<int> f;
auto future_result = f.get_future();//get_future()用于获取异步操作的结果
//get_future() 是一个方便的函数,用于获取与异步操作关联的
// std::future 对象,从而实现异步操作的结果获取和等待。
std::thread t1(func, std::ref(f));//开启线程
t1.join();
std::cout << future_result.get() << std::endl;
//get() 是 std::future 类模板的成员函数,用于获取与异步操作关联的结果。
// 当异步操作完成后,可以使用 get() 函数来获取其结果。
return 0;
}
4.原子操作atomic
`std::atomic` 是 C++11 标准库中的一个模板类,用于实现多线程环境下的原子操作。它提供了一种线程安全的方式来访问和修改共享变量,可以避免多线程环境中的数据竞争问题。
`std::atomic` 的使用方式类似于普通的 C++ 变量,但是它的操作是原子性的。也就是说,在多线程环境下,多个线程同时对同一个 `std::atomic` 变量进行操作时,不会出现数据竞争问题。
相较于加锁解锁操作更加安全并且效率更高
以下是一些常用的 `std::atomic` 操作:
1. `load()`:将 `std::atomic` 变量的值加载到当前线程的本地缓存中,并返回这个值。
也就是输出这个值
2. `store(val)`:将 `val` 的值存储到 `std::atomic` 变量中,并保证这个操作是原子性的。
也就是修改`std::atomic` 变量的值
3. `exchange(val)`:将 `val` 的值存储到 `std::atomic` 变量中,并返回原先的值。
4. `compare_exchange_weak(expected, val)` 和 `compare_exchange_strong(expected, val)`:比较 `std::atomic` 变量的值和 `expected` 的值是否相同,如果相同,则将 `val` 的值存储到 `std::atomic` 变量中,并返回 `true`;否则,将 `std::atomic` 变量的值存储到 `expected` 中,并返回 `false`。
以下是实例代码
#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;
}