所需头文件
#include "stdafx.h"
#include <iostream>
#include <thread>
#include <future>
using namespace std;
一.std::future的其他成员函数
上一篇使用了future的get()成员函数,这一篇用范例说明一下其他成员函数,主要是future_status这个枚举类型
线程入口函数
int MyThread()
{
cout << "MyThread start " << "thread id = " << std::this_thread::get_id() << endl; //新的线程ID
std::chrono::milliseconds duration(5000); //5000ms
std::this_thread::sleep_for(duration); //程序停止运行5s
cout << "MyThread end " << "thread id = " << std::this_thread::get_id() << endl;
return 5;
}
主函数
int main()
{
cout << "main " << "thread id=" << std::this_thread::get_id() << endl;
//std::future<int> result = std::async(MyThread);
std::future<int> result = std::async(std::launch::deferred,MyThread); //延迟执行
cout << "continue..." << endl;
//cout << result.get() << endl; //卡在这里等待线程执行完毕
//枚举类型:
//std::future_status status = result.wait_for(std::chrono::seconds(1)); //希望等待1s就得到返回值
std::future_status status = result.wait_for(std::chrono::seconds(6)); //希望等待6s就得到返回值
if (status == std::future_status::timeout) //超时(未返回):表示线程还没执行完
{
cout << "线程还未执行完毕" << endl;
}
else if (status == std::future_status::ready) //线程成功返回
{
cout << "线程成功执行完毕,返回" << endl;
cout << result.get() << endl;
}
else if (status == std::future_status::deferred)
{
//如果async的第一个参数被设置为std::launch::deferred,这本条件成立
cout << "线程被延迟执行" << endl;
cout << result.get() << endl; //调用get后线程入口函数才会执行,且在主线程中执行
}
cout << "hello world" << endl;
return 0;
}
二.std::shared_future:类模板
其get函数复制数据,而不是像std::future那样移动数据,则多个线程就可以获取同一个线程的数据了,即多次调用get也不会报出异常
下面进行举例测试:
线程入口函数一:
int MyThread1(int val)
{
cout << "MyThread1 start " << "thread id = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds duration(5000); //5000ms=5s
std::this_thread::sleep_for(duration);
return 5;
}
线程入口函数二:
void MyThread2(std::shared_future<int>& tmp)
{
cout << "MyThread2 start " << "thread id = " << std::this_thread::get_id() << endl;
auto result = tmp.get(); //获取值
cout << "MyThread2 result= " << result << endl;
return;
}
主函数
先将线程入口函数一的返回值保存到std::future对象中,再传递给std::shared_future。
int main()
{
cout << "main " << "thread id=" << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> myPT(MyThread1); //把函数MyThread1通过packaged_task包装起来
std::thread T1(std::ref(myPT), 1); //线程开始执行,第二个参数作为线程入口函数的参数
T1.join(); //调用join等待线程执行完毕,不用这个的话用get等待程序会崩溃
std::future<int> result = myPT.get_future();
bool ifcanget = result.valid(); //valid函数可以得到result是否有有效值,此时应该为true
//std::shared_future<int> result_shared(std::move(result));//需要用std::move转成右值类型
std::shared_future<int> result_shared(result.share()); //这样也可以,执行完毕后,result_shared中有值,而result里空了
ifcanget = result.valid(); //此时应该为false,result中的值已经移动到result_shared中
auto MyThreadResult = result_shared.get();
MyThreadResult = result_shared.get(); //可以多次get
std::thread T2(MyThread2, std::ref(result_shared));
T2.join(); //等待线程执行完毕
cout << "hello world" << endl;
return 0;
}
下面我们直接将结果保存到shared_future中,而不借助于future
修改主函数如下:
int main()
{
cout << "main " << "thread id=" << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> myPT(MyThread1); //把函数MyThread1通过packaged_task包装起来
std::thread T1(std::ref(myPT), 1); //线程开始执行,第二个参数作为线程入口函数的参数
T1.join(); //调用join等待线程执行完毕,不用这个的话用get等待程序会崩溃
//直接保存到shared_future
std::shared_future<int> result_shared(myPT.get_future());//通过get_future直接构造shared_future对象
auto MyThreadResult = result_shared.get();
MyThreadResult = result_shared.get(); //可以多次get
std::thread T2(MyThread2, std::ref(result_shared));
T2.join(); //等待线程执行完毕
cout << "hello world" << endl;
return 0;
}
三.原子操作std::atomic
3.1)原子操作概念引出范例
前面的文章讲了互斥量:多线程编程中保护共享数据:锁,操作共享数据,开锁
互斥量可以解决这样的问题:有两个线程对一个变量进行操作,一个线程读,另一个线程向这个变量写,从底层汇编代码来看,写操作有很多步骤,被打断就会导致意外结果出现
下面先进行不加互斥量测试:
int global_value = 0; //定义一个全局变量
//线程入口函数
void MyThread()
{
for (int i = 0; i < 1000000; i++)
{
global_value++; //修改全局变量
}
}
主函数
int main()
{
thread T1(MyThread);
thread T2(MyThread);
//创建两个线程,都会对全局变量修改
T1.join();
T2.join();
cout << "两个线程执行完毕,最终结果:" << endl;
cout << global_value << endl;
return 0;
}
程序执行结果如下,理想情况下,两个线程都对同一个全局变量进行加操作1000000次,最后的结果应改为2000000,但是经过多次测试,每次的执行结果都不一样,因为出现了上面说的写操作被打断的情况。
下面进行加互斥量测试:
int global_value = 0; //定义一个全局变量
std::mutex global_mutex; //互斥量
//线程入口函数
void MyThread()
{
for (int i = 0; i < 1000000; i++)
{
global_mutex.lock(); //加锁
global_value++;
global_mutex.unlock(); //解锁
}
}
主函数不用修改
从下面的执行结果可以看出,得到了正确的计算结果。
如果上述写操作执行的次数再多一些,我们会发现程序执行速度明显变慢,所以有没有什么其他方法可以达到类似于加互斥量的效果呢?就是我们提出的原子操作
可以把原子操作理解成一种:不需要用互斥量加锁(无锁)技术的多线程并发编程方式
原子操作是在多线程中不会被打断的程序执行片段,比互斥量效率上更胜一筹
互斥量的加锁一般针对一个代码段(几行代码),而原子操作针对的一般是一个变量
原子操作,一般都是指“不可分割的操作”,也就是说这种操作状态要么已完成,要么没完成,不会出现中间状态
std::atomic代表原子操作,std::atomic是一个类模板,其实是用来封装某个类型的值
3.2)基本std::atomic用法范例
修改int类型的定义即可,并删除有关互斥量的代码,如下:
std::atomic<int> global_value = 0; //封装了一个类型为int的atomic对象,可以像操作int型变量来操作这个对象
//线程入口函数
void MyThread()
{
for (int i = 0; i < 1000000; i++)
{
global_value++; //对应的操作是原子操作(不会被打断)
}
}
上述程序运行后的结果如下,可以明显看出运行速度快于互斥量加锁。
下面再举一个栗子:
程序需要实现一个bool变量控制线程退出,故线程一直在运行中查询此变量的状态,而主线程中会对此变量进行修改,此变量需用atomic进行封装。
线程入口函数
std::atomic<bool> global_ifend = false; //线程退出标记,原子操作,防止读写乱套
void MyThread()
{
std::chrono::milliseconds duration(1000); //1000ms=1s
while (global_ifend == false)
{
//系统没要求程序退出
cout << "thread id= " << std::this_thread::get_id() << "运行中..." << endl;
std::this_thread::sleep_for(duration);
}
cout << "thread id= " << std::this_thread::get_id() << "运行结束..." << endl;
return;
}
主函数
int main()
{
thread T1(MyThread);
thread T2(MyThread);
std::chrono::milliseconds duration(5000); //5000ms=5s
std::this_thread::sleep_for(duration);
global_ifend = true; //对原子对象写操作,让线程运行结束
T1.join();
T2.join();
cout << "程序执行完毕,退出" << endl;
return 0;
}
以下执行结果为:主函数开始运行5s后修改原子对象的值,进而控制线程退出。
3.3)总结:
原子操作一般用于计数或统计(累计发送/接收了多少个数据包)
其他使用的地方不多
网友评论