《1》std::future的其他成员函数
1.1》std::future的.get()成员函数:可以获取对应绑定到std::future身上的线程的入口函数的返回值,且get()函数只能调用一次!
demo_codes:
#include<iostream>
#include<thread>
#include<chrono>
#include<future>
using namespace std;
int mythread() {
cout << "mythread() start " << " threadid = " << std::this_thread::get_id() << endl;//新的线程id
std::chrono::milliseconds dura(5000);//定义休息的时间为5s
std::this_thread::sleep_for(dura);//休息一定时长
cout << "mythread() end " << " threadid = " << std::this_thread::get_id() << endl;//新的线程id
return 5;
}
int main(void) {
cout << "main threadid = " << std::this_thread::get_id() << endl;
std::future<int> result = std::async(mythread);//async
cout << "continue ..." << endl;
auto res = result.get();//get函数获取线程函数返回值
cout << "res = " << res << endl;
cout<<"main end!"<<endl;
return 0;
}
1.2》std::future的.wait_for()成员函数:会等待一定的时间,并返回一个std::future_status的枚举类型。
enum class future_status { // names for timed wait function returns
ready,
timeout,
deferred
};//该枚举类型有3种状态,分别是ready、timeout、deferred
demo_codes:(由于ready和timeout状态很简单,这里就不给出其对应的test-codes了。这里只给出我测试deferred状态的代码!我的注释也要看,这很重要!)
#include<iostream>
#include<thread>
#include<chrono>
#include<future>
using namespace std;
int mythread() {
cout << "mythread() start " << " threadid = " << std::this_thread::get_id() << endl;//新的线程id
std::chrono::milliseconds dura(5000);//定义休息的时间为5s
std::this_thread::sleep_for(dura);//休息一定时长
cout << "mythread() end " << " threadid = " << std::this_thread::get_id() << endl;//新的线程id
return 5;
}
int main(void) {
cout << "main threadid = " << std::this_thread::get_id() << endl;
std::future<int> result = std::async(std::launch::deferred,mythread);//async
cout << "continue ..." << endl;
//枚举类型
std::future_status status = result.wait_for(std::chrono::seconds(6));//等待1s钟!
if (status == std::future_status::timeout) {
//当future对象result 等待对应绑定到它身上的线程入口函数等待了6s钟后,若线程入口函数仍然未执行完毕的话
//则.wait_for函数会返回一个timeout的状态!
cout << "子线程运行还没结束呢!超时啦!" << endl;
}
else if (status == std::future_status::ready) {
//当future对象result 等待对应绑定到它身上的线程入口函数等待了6s钟后,若线程入口函数已经执行完毕的话
//则.wait_for函数会返回一个ready的状态!
cout << "子线程运行已经结束了!" << endl;
auto res = result.get();
cout << "res = " << res << endl;
}
else if (status == std::future_status::deferred) {
//当std::async函数模板的第一个参数标记为std::launch::deferred时,会返回该std::future_status::deferred枚举类型
//也即std::async函数模板的第一个参数标记为std::launch::deferred时本条件成立!
//且,上一节课我们学了,当async函数的第一个参数为launch::deferred时,会延迟对应绑定的线程入口函数的执行
//且,系统将一直延迟这子线程入口函数的执行,当后续代码中future对象没有用.wait()或者.get()函数时,此时就不会再执行该子线程入口函数了!
//则.wait_for函数会返回一个deferred的状态!
cout << "子线程入口函数根本就没有执行!" << endl;//或者说该子线程函数被延迟执行!
}
cout<<"main end!"<<endl;
return 0;
}
运行结果:
即便你把上述的std::future_status::deferred的条件语句改成如下:
else if (status == std::future_status::deferred) {
auto res = result.get();
cout << "res = " << res << endl;
cout << "子线程入口函数根本就没有执行!" << endl;
}
也不能够创建新的子线程,而仅仅是在主线程去调用该线程入口函数罢了~(下图可验证我说的这一点)
再次运行的结果:
都是同一个线程id,说明都是主线程来执行这段代码的,而没有创建新的子线程来do!
《2》std::shared_future
问题:前面所说的std::future对象能拿到线程入口函数执行完毕之后的返回值,但是,该对象要获取该返回值还需要使用成员函数.get(),且只能用一次!那么如果说当有多个线程都需要用到该返回值时,这就行不通了吧?是的!
回答:因此,就引入std::shared_future函数!
std::shared_future:和std::future类模板的对象类似,都可以拿到线程入口函数的返回值!
格式:
std::shared_future<线程入口函数返回值的类型> sharedfutureObjName;
注意:
//std::future和std::shared_future的主要区别是
std::future的get函数是移动(move)数据(这样就使得只有另外1个线程可以使用这个返回值数据了)
因此,future的get函数只能使用1次!
std::shared_future的get函数是复制(copy)数据(这样就使得多个线程都可以使用这个返回值数据了)
因此,shared_future的get函数能使用多次!
demo1_codes:
#include<iostream>
#include<thread>
#include<chrono>
#include<future>
using namespace std;
int mythread(int val) {
cout << "mythread() start " << "threadid = " << std::this_thread::get_id() << endl;//新的线程id
std::chrono::milliseconds dura(5000);//定义休息的时间为5s
std::this_thread::sleep_for(dura);//休息一定时长
cout << "mythread() end " << " threadid = " << std::this_thread::get_id() << endl;//新的线程id
return 5;
}
void mythread2(std::shared_future<int>& tmf) {
cout << "mythread2() start " << "thread id = " << std::this_thread::get_id() << endl;//线程id
auto res = tmf.get();//获取线程返回值,注意,任何时,只能get一次,否则会报异常!
auto res2 = tmf.get();
cout << "res1 = " << res << ",res2 = " << res2 << endl;
cout << "mythread2() end " << " threadid = " << std::this_thread::get_id() << endl;//线程id
return;
}
int main(void) {
//2-std::shared_future
cout << "main threadid = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt(mythread);//我们把mythread这个线程函数通过packaged_task包装起来
std::thread t1(std::ref(mypt), 1);//线程直接开始执行,第二个参数为线程入口函数的参数
t1.join();
//调用join函数让主线程等待子线程执行完毕,再和主线程回合一起往下执行!
std::future<int> res = mypt.get_future();//packaged_task对象里面用get_future就可以传给future对象
//并让res这个future对象拿到线程1结束后的返回值
//bool ifchanget = res.valid();
std::shared_future<int> res_s(std::move(res));//这行代码执行完毕之后,res_s有值了!但是res为空了!且 std::move(res) ==> res.share()
//std::shared_future<int> res_s(res.share());//这行代码执行完毕之后,res_s有值了!但是res为空了!
//ifchanget = res.valid();
auto threadretres1 = res_s.get();
auto threadretres2 = res_s.get();
auto threadretres3 = res_s.get();
//std::thread t2(mythread2, std::ref(res));//第2个参数为线程入口函数2的参数
std::thread t2(mythread2, std::ref(res_s));//第2个参数为线程入口函数2的参数
t2.join();
cout << "main end!" << std::this_thread::get_id() << endl;
return 0;
}
运行结果:
demo2_codes:
#include<iostream>
#include<thread>
#include<chrono>
#include<future>
using namespace std;
int mythread(int val) {
cout << "mythread() start " << "threadid = " << std::this_thread::get_id() << endl;//新的线程id
std::chrono::milliseconds dura(5000);//定义休息的时间为5s
std::this_thread::sleep_for(dura);//休息一定时长
cout << "mythread() end " << " threadid = " << std::this_thread::get_id() << endl;//新的线程id
return 5;
}
void mythread2(std::shared_future<int>& tmf) {
cout << "mythread2() start " << "thread id = " << std::this_thread::get_id() << endl;//线程id
auto res = tmf.get();//获取线程返回值,注意,任何时,只能get一次,否则会报异常!
auto res2 = tmf.get();
cout << "res1 = " << res << ",res2 = " << res2 << endl;
cout << "mythread2() end " << " threadid = " << std::this_thread::get_id() << endl;//线程id
return;
}
int main(void) {
//2-std::shared_future
cout << "main threadid = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt(mythread);//我们把mythread这个线程函数通过packaged_task包装起来
std::thread t1(std::ref(mypt), 1);//线程直接开始执行,第二个参数为线程入口函数的参数
t1.join();
std::shared_future<int> res_s(mypt.get_future());
//通过packaged_task对象中的.get_future()函数的返回值std::future对象用于构造一个shared_future对象!(这里涉及到了隐式类型转换!)
std::thread t2(mythread2, std::ref(res_s));//第2个参数为线程入口函数2的参数
t2.join();
cout << "main end!" << std::this_thread::get_id() << endl;
return 0;
}
运行结果:
《3》原子操作std::atomic
在C++11中,引入了std::atomic这个类模板来进行原子操作。
(std::atomic就是用来封装这个变量类型的值的!)
格式:
std::atomic<要封装的变量的类型> atomicObj;//or atomicObj = 初始值;也ok
3.1》原子操作概念引出范例
互斥量:在多线程编程中,是用来保护共享数据的!(这非常重要)
(人话:当某个线程使用or操作共享数据时,就用mutex互斥量这把锁头锁住,让其顺利地执行完毕而不是让操作系统给你切到别的线程去。当操作完成该段共享数据的代码时,锁头就打开,再让别的需要使用该共享数据代码的线程去用!你用完我再用,一个一个排着队!井然有序!简言之:本线程:锁住 操作共享数据 开锁;其他要使用该共享数据的线程:锁住 操作共享数据 开锁,本线程没开锁前,别的要用共享数据的线程就得在这儿给我等着,等我解锁了再说!)
我们除了可以使用互斥量来避免多个线程在操作同一份共享数据时产生的错误!其实,我们还可以使用原子操作来避免这类错误!
原子操作:是在多线程中 不会被打断的 程序执行某个代码行。也指的是“不可分割的操作”,即:这种操作的状态要么是已完成,要么是未完成,不可能出现半完成状态。我们可以把原子操作理解成一种:不需要用到互斥量加锁技术的多线程并发编程方式!(也即使一种无锁的多线程并发编程方式)也正是因为无锁,所以在保护共享数据代码时,原子操作比互斥量(mutex锁)在效率上要更胜一筹!
注意:互斥量和原子操作保护共享数据的代码的效果是一致的!但是,互斥量和原子操作各有各的适用范围!
互斥量的加锁一般是针对一个代码段(多行代码)
原子操作一般是针对某一个对象(变量)(一行代码)(只能应付多线程中对于单个变量的读和写的变化的保护)
因此,当你想保护某个共享数据的对象时,用原子操作较好,想保护某段共享数据代码时,用互斥量加锁解锁的方式较好!
用互斥量保护共享数据的代码:
demo_codes:
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
int g_value = 0;
std::mutex my_g_mutex;
//写值线程入口函数
void mythread() {
for (int i = 0; i < 1000000; i++) {
//std::unique_lock<std::mutex> unilockObj(my_g_mutex);
//g_value++;
//这个unique_lock对象就保证我们一定能够让每次线程进入到for循环时都能执行完g_value++; 这一行代码的操作
//这样就不会因为操作系统的调度切换导致g_value这个变量没有++成功这种情况的发生!!!
// or
my_g_mutex.lock();
g_value++;
my_g_mutex.unlock();
}
return;
}
int main(void) {
//3-原子操作:std::atomic
cout << "main threadid = " << std::this_thread::get_id() << endl;
//当有2个线程,一个对变量进行读取值的操作,另一个线程也对这个变量中进行读取值的操作
//或者2个线程同时对一个变量进行写的操作时,都会产生共享数据时线程之间因操作系统的调度机制导致
//所操作的共享数据出错的可能!
std::thread thd1(mythread);
std::thread thd2(mythread);
thd1.join();
thd2.join();
//创建2个线程同时对于全局变量g_value进行写的操作!
cout << "2个线程都执行完毕了!现在的 g_value = " << g_value << endl;
cout << "main end!" << std::this_thread::get_id() << endl;
return 0;
}
此时,无论运行多少次,结果都是g_value == 200w:
若不用mutex互斥量or原子操作的话,一定是会在某次结果上出错的!也即g_value的值不总会是200w了的意思!
3.2》基本的std::atomic用法范例
用原子操作来保护共享数据的代码:
#include<iostream>
#include<thread>
#include<mutex>
#include<atomic>
using namespace std;
//int g_value = 0;
std::atomic<int> g_value = 0;//定义了一个具备原子操作的整型对象g_value
//这个g_value就可以当做是一个int型变量一样去操作!
//写值线程入口函数B
void mythread() {
for (int i = 0; i < 100000000; i++) {
g_value++;
//此时,因为g_value已经是一个原子类型了,因此它对应的操作就算原子操作了(即不会因为被操作系统的调度机制切换线程而打断,可以正常执行下去)!
}
return;
}
int main(void) {
cout << "main threadid = " << std::this_thread::get_id() << endl;
std::thread thd1(mythread);
std::thread thd2(mythread);
thd1.join();
thd2.join();
//创建2个线程同时对于全局变量g_value进行写的操作!
//基本的std::atomic用法范例!
cout << "2个线程都执行完毕了!现在的 g_value = " << g_value << endl;
cout << "main end!" << std::this_thread::get_id() << endl;
return 0;
}
运行结果:
原子操作demo2_codes:
#include<iostream>
#include<thread>
#include<chrono>
#include<mutex>
#include<atomic>
using namespace std;
std::atomic<bool> g_ifend = false;//线程退出标记!这里是原子操作!防止在多线程操作该对象时,造成的数据乱套问题!
void mythread() {
std::chrono::seconds dura(1);//定义一个1s钟的对象
while (g_ifend == false) {
//此时系统没要求线程退出,所有本线程可以干自己想干的事儿!
cout << "thread id = " << std::this_thread::get_id() << " 运行中..." << endl;
std::this_thread::sleep_for(dura);//休息1s钟!
}
cout << "thread id = " << std::this_thread::get_id() << " 运行结束!" << endl;
return;
}
int main(void) {
cout << "main threadid = " << std::this_thread::get_id() << endl;
std::thread thd1(mythread);
std::thread thd2(mythread);
//定义这2个子线程的同时,他们就会开始执行!
//然后让主线程休息5s钟!
std:chrono::seconds dura(5);
std::this_thread::sleep_for(dura);
g_ifend = true;//对原子对象进行写操作,进而让子线程自动结束运行!
thd1.join();//让thd1子线程与主线程汇合!
thd2.join();//让thd1子线程与主线程汇合!
//基本的std::atomic用法范例!
//cout << "2个线程都执行完毕了!现在的 g_value = " << g_value << endl;
cout << "main end!" << std::this_thread::get_id() << endl;
return 0;
}
运行结果:
虽然std::atomic这个进行原子操作的类模板还有许多的成员函数,但是wjw老师说那些成员函数基本很少用,要是强行拿出来写一段测试代码来学习这些成员函数的话没有啥必要,万一日后工作中用到了,可以自行翻书自学哈!毕竟在本节课我们已经知道了原子操作的基本概念和用法了!有基础就不怕深入学习了!
3.3》wjw老师对于std::atomic的心得
wjw老师使用std::atomic的场景不多,一般就是用于在多个线程中进行统计计数时会用到,比如:多个线程累计发送出去多少个数据包,累计接收了多少个数据包。
无论何时,在工作中,都要写出自己最拿得准的代码,你模棱两可时可以自己写一小段测试代码来验证自己的想法,但千万不要提交一些模棱两可的代码给到别人!