C++编程笔记(多线程学习)

一、线程创建

每个程序都有一个主线程,主线程的入口函数就是main函数,一般情况下,主线程结束,无论子线程是否执行完毕,子线程都将结束

#include <iostream>
#include <thread>
#include <string>

using std::cout;
using std::thread;

//线程入口函数,函数完毕,线程结束
void MyPrint() {
    cout << "This is MyThread!\n";

    cout << "MyThread is finish!\n";
}

int main() {
    cout << "你好\n";
    
    thread MyThread1(MyPrint);//创建子线程,接收可调用对象
    cout<<"Main finish!\n";
    return 0;
}

二、线程的相关操作

2.1 join

阻塞函数
join: 阻塞函数,在这段代码中表示,主线程阻塞,等待MyThread1运行完后再结束

int main() {
    cout << "你好\n";

    thread MyThread1(MyPrint);//接收可调用对象

    MyThread1.join();//jion,阻塞,等待MyThread运行结束后在继续运行主线程
    

    cout<<"Main finish!\n";
    return 0;
}

2.2 detach

分离函数
detach:分离函数,分离子线程主线程,调用后,主线程是否结束不会影响子线程的运行
用的比较少,因为子线程不可控

int main() {
    cout << "你好\n";

    thread MyThread1(MyPrint);

    MyThread1.detach();
    
    cout<<"Main finish!\n";
    return 0;
}

调用detach后,线程转入后台运行。此时被C++运行时库接管,当子线程执行完毕后,由运行时库负责清理线程资源。
一旦detach,就无法join,因为失去了关联。
所以detach应用的情况相对较少

2.3 joinable

判断函数
joinable:判断线程是否能join或者detach,返回turefalse

因为很多时候我们不知道某个线程能否join或者detach,所以需要用这个进行判断

三、线程参数

3.1传参所引发的资源回收问题

void myprint(const int &i,char* p){
	cout<<i<<endl<<p<<endl;
}
int main(){
	int a=10;
	char mystr[]="hello world";
	thread myth(myprint,myI,mystr);//线程传参
	myth.join()
	//myth.detach();
}

看上述例子,如果detach分离了线程的话,有可能造成主线程已经结束,但是myth未结束的情况,则此时str已经被系统回收,p所指向的内容就不确定,程序出错
所以,为了避免资源被系统回收,使用临时变量

thread myth(myprint,myI,string(mystr));//线程传参

当使用了临时对象后,便没有出现资源被回收程序出问题的情况


为什么使用临时对象就不会出现问题?
猜想:使用临时对象是进行了资源的拷贝?
为了验证猜想,自己写一个类

class TA {
    int a;
public:
    void operator()() {//仿函数
        cout << "线程TA开始执行了" << endl;
    }
    TA() = default;

    TA(int b) {
        this->a = b;						//获取当前线程的id
        cout << "构造函数!TA() id = " << std::this_thread::get_id() << "\taddr = " << this << endl;
    }

    TA(const TA &t) {
        this->a = t.a;
        cout << "拷贝构造TA(const TA & t) id = " << std::this_thread::get_id() << "\taddr = " << this << endl;
    }
    void work(int num){
        cout<<"子线程work执行 "<< this<<" id = "<<std::this_thread::get_id()<<endl;
    }
    ~TA() {
        cout << "析构函数 id = " << std::this_thread::get_id() << "\taddr = " << this << endl;
    }
};
void myprint2(const TA &ta) {//class类型参数 即使是引用,仍会进行拷贝,值传递
    cout << "子线程开始 " << "addr = " << &ta << "\t子线程id = " << std::this_thread::get_id() << endl;
}

传入a为int类型,可是函数参数类型为TA,此时会发生隐式类型转化,调用构造函数,将int转为TA类型

int main(){
	cout << "main id = " << std::this_thread::get_id() << endl;
	int a=1
	thread myobj(myprint2, a);//发现对象构造是在子线程中
	myobj.join();
}

运行结果:


main id = 139656476180864
构造函数!TA() id = 139656476165696	addr = 0x7f044ea6fd74
子线程开始 addr = 0x7f044ea6fd74	子线程id = 139656476165696
析构函数 id = 139656476165696	addr = 0x7f044ea6fd74

可以看出,由于入口函数参数为类型TA,程序发生隐式转换,用变量a构造了一个临时对象,再将临时对象传入入口函数。并且可以观察到,构造对象的过程并不是在主线程完成的,而是在子线程中进行。所以我们可以推测,如果使用detach,就有可能造成主线程比子线程先结束,导致a被回收,从而构造出现问题,引发程序异常!大家不妨可以试试join改为detach,多运行几次可能就会发现问题所在。

那我们如何解决这类问题呢


不要使用隐式转换,自己完成强转操作

int main(){
	cout << "main id = " << std::this_thread::get_id() << endl;
	int a=1
	thread myobj(myprint2, TA(a));
	myobj.join();
}

运行结果:


main id = 139705515577728
构造函数!TA() id = 139705515577728	addr = 0x7ffd013a24a8
拷贝构造TA(const TA & t) id = 139705515577728	addr = 0x55a7c06ad2c8
析构函数 id = 139705515577728	addr = 0x7ffd013a24a8
子线程开始 addr =  0x55a7c06ad2c8	子线程id = 139705515562560
析构函数 id = 139705515562560	addr = 0x55a7c06ad2c8

通过上一例子可以看出,首先在主线程构造了一个临时变量,由 TA(a)引起,因为这一对象很快被析构了,所以我们判断它是临时对象,这很容易理解;然后发生了拷贝构造,并且拷贝构造依然发生在主线程,并且可以看到,拷贝出来的对象进入到了子线程去执行,我们就可以推测,临时对象作为入口函数的参数,子线程会对其进行拷贝。
因此,在detach一个线程时,我们尽量不要让编译器对参数进行隐式类型转化,这样有可能引发传入资源被回收的问题!对应的解决办法就是自己对其进行转化


我们还可以看出,即使子线程入口函数的的参数是引用类型,传参是仍然是拷贝构造,相当于就是值传递,那如果我们需要子线程对某一数据进行更改,需要传入引用怎么办呢?
使用std::ref()

int main(){
	cout << "main id = " << std::this_thread::get_id() << endl;
	int a=1
	TA ta(a);
	thread myobj(myprint2, std::ref(ta));
	myobj.join();
}

运行结果:


main id = 139781778977152
构造函数!TA() id =  139781778977152	addr = 0x7fffd0ec9d00
子线程开始 addr = 0x7fffd0ec9d00	子线程id = 139781778961984
析构函数 id = 139781778977152	addr = 0x7fffd0ec9d00

可以很清除的看到,当我们使用了std::ref()时,构造函数在主线程中进行,并且子线程没有发生拷贝构造。传给子线程的是一个引用。此时我们将入口函数参数列表中的const修饰符去掉,就可以修改对象中的数据,编译器也不会报错。这种情况下应该注意被引用对象回收时间的问题。

3.2 将对象的成员函数作为入口函数

int main(){
	cout << "main id = " << std::this_thread::get_id() << endl;
	int a=1
	TA ta(a);
	thread myobj(&TA::work,ta, 1);//类的成员函数作线程入口,第三个参数传入的对象,第四个参数是work函数的参数 
	
	// 也可以使用引用
	//thread myobj(&TA::work,&ta, 1); 等价于std::ref(ta)   不会调用拷贝构造
	myobj.join();
}

类内线程创建

class Ta{
	public:
		Ta(){
			std::thread t(std::bind(&Ta::task,this));
		}
		void task();

}

四、线程的互斥量的使用

       很多时候,多个线程需要对同一数据进行操作,同时读取某一数据往往不会出现问题,而当由某些线程读取数据,某些线程又会写入数据时,往往会发生读写冲突。
       我们试想一下,线程A在读取读取数据Data时,时间片结束,系统开始运行线程B,而线程B恰好要对Data进行修改,那么是不是会发生冲突。再想一下,一个线程对文件进行写入操作,结果写入结束还没保存并关闭文件时,时间片结束,另一个线程也访问这个文件,可是由于写文件线程没有保存文件,那么访问线程不就拿不到数据了吗。
       所以引入了互斥量这一概念

当访问共享数据前,使用互斥量将相关数据锁住,再当访问结束后,再将数据解锁。多线程程序需要保证,当一个线程使用特定互斥量锁住共享数据时,其他的线程想要访问锁住的数据,都必须等到之前那个线程对数据进行解锁后,才能进行访问。这就保证了所有线程能看到共享数据

举个栗子

/*  假如说我们有很多用户,他对我们的系统下了很多指令,使用一个线程接收指令,将指令存入一个list中
*   再由另一个线程读取list中的指令
*/
class ReadWrite {
private:
    std::list<int> M;
public:
    void RecvMsg() {				//接收指令的线程
        for (size_t i = 0; i < 10000; ++i) {
            cout << "RecvMsg insert a num=" << i << endl;         
            M.emplace_back(i);           
        }
    }
    void GetFromM() {				 //读取指令的线程
        for (size_t i = 0; i < 10000; ++i) {           
            if (!M.empty()) {
                int com = M.front();    //返回地一个元素,无论是否为空
                M.pop_front();          //取出后移除
                cout<<"cmd="<<com<<endl;               
            } else {                
                cout << "list is null\ti=" << i << endl;
            }
        }
        cout << "Get end" << endl;
    }
};

int main(){
    ReadWrite r;
    thread out(&ReadWrite::GetFromM, std::ref(r));
    thread get(&ReadWrite::RecvMsg, std::ref(r));
    get.join();
    out.join();
    cout << "main end" << endl;
}

       上述代码在运行时会发生问题,由于涉及到了同步读写同一块内存区域的问题,要解决这个问题,需要引用mutex互斥量这个概念

#include<mutex>
class ReadWrite {
private:
    std::list<int> M;
public:
    void RecvMsg() {				//接收指令的线程
        for (size_t i = 0; i < 10000; ++i) {
            cout << "RecvMsg insert a num=" << i << endl;
            mymutex.lock();
            M.emplace_back(i);
            mymutex.unlock();        
        }
    }
    void GetFromM() {				 //读取指令的线程
        for (size_t i = 0; i < 10000; ++i) {     
        	mymutex.lock();      
            if (!M.empty()) {
                int com = M.front();    //返回第一个元素,无论是否为空
                M.pop_front();          //取出后移除
                cout<<"cmd="<<com<<endl;               
            } else {                
                cout << "list is null\ti=" << i << endl;
            }
            mymutex.lock();
        }
        cout << "Get end" << endl;
    }
};

int main(){
    ReadWrite r;
    thread out(&ReadWrite::GetFromM, std::ref(r));
    thread get(&ReadWrite::RecvMsg, std::ref(r));
    get.join();
    out.join();
    cout << "main end" << endl;
}

引入mutex,在对数据进行读写的时候,将把锁锁上,然后再操作数据,这样其他线程在试图访问的时候,由于锁被锁住了,其他线程也无法进行上锁操作,就会被阻塞,数据操作结束,将锁释放,让其他线程可以操作数据。
说的通俗一点,就像上厕所一样,上厕所之前要把门(mutex)先锁好(lock),这样其他人访问厕所就会被门拦住,上完了后将门打开(unlock),厕所就能被其他人用了
有一点一定要记住,就是加锁和解锁一定是成对出现的,否则其他线程就会进入无限等待的状态。所以C++标准库提供了类似智能指针的锁std::lock_guard,可以自动上锁自动解锁,当锁变量离开作用域时就会自动解锁,除此之外还可以使用std::unique_lock,相较于lock_guard更为灵活,它由不同的成员函数由用户自己控制何时加锁,能否加锁
unique_lock详解

在使用互斥量的时候,一定要注意死锁,死锁就是两个线程同时等待对方持有的某一资源,导致程序无法正常推进
比如说我们由两个锁AB线程1线程2线程一需要先锁A,然后锁B才能继续进行数据访问操作,而线程二需要先锁B再锁A才能进行数据访问操作。假设程序运行到某一时间点,线程一锁住了A线程二锁住了B,而此时,线程一需要将B上锁,才能继续推进,可是B已经被线程二锁住了,于是线程一阻塞了,必须等待B被解锁,同时线程二也是要等待A被解锁才能继续推进,程序再此刻便发生了死锁
解决死锁,只需要上锁的顺序一致便可,即线程一和线程二都是先上锁A再上锁B。使用std::lock就可以保证多个锁的顺序一致


除了std::mutex外,C++还提供了递归的独占互斥量std::recursive_mutex,它允许一个线程,同一个互斥量被lock多次

timed_mutex和recursive_timed_mutex

带超时功能的独占互斥量和带超时功能的递归独占互斥量

两个接口:try_lock_fortry_lock_until
try_lock_for:等待一段时间,超时未拿到锁则放弃,程序继续执行,参数为std::chrono::*

std::timed_mutex my_mutex;
std::chrono::milliseconds my_times(100);//100毫秒
my_mutex.try_lock_for(my_times);//等待100毫秒,如果100毫秒内获得锁,则返回false

try_lock_until:等待到某一个时间点

std::timed_mutex my_mutex;
std::chrono::milliseconds my_times(100);//100毫秒
my_mutex.try_lock_until(std::chrono::steady_clock::now()+my_times);//等待100毫秒,如果100毫秒内获得锁,则返回false
shared_mutex读写锁

可以理解为读写锁,允许多人读,单人写,但是写和读,写和写不能同时进行

可以直接locklock_shared,前者表示写的方式,一旦上锁,其他线程无法进入,后者表示读的方式,上锁后其他线程只能读,无法写


五、条件变量

5.1condition_variable

条件变量condition_vriable需要搭配互斥量使用,它是一个类,包含在同名的头文件中,有一些自己的成员函数,这里主要介绍notify_onewait函数

首先看一个例子

class MsgList {
private:
    list<int> Command;
    std::mutex mutex1;
    std::condition_variable MyCod;
public:
    void GetMsg() {
        int cmd = 0;
        while (true) {
            std::unique_lock<std::mutex> guard(this->mutex1);//条件变量要与互斥量配合使用
            this->MyCod.wait(guard, [this]() {
                return !this->Command.empty();
            });
            cmd = Command.front();
            Command.pop_front();
            guard.unlock();
            cout << "读取指令" << cmd << endl;
        }
    }
    void PutMsg() {
        for (size_t i = 0; i < 100000; ++i) {
            std::unique_lock<std::mutex> guard(this->mutex1);
            cout << "插入指令:" << i << endl;
            Command.emplace_back(i);
            this->MyCod.notify_one();
            guard.unlock();//可要可不要,unique_lock的好处就在于可随时控制
        }
    }
};
int main(){
    MsgList msgList;
    thread Get(&MsgList::GetMsg, &msgList);
    thread Put(&MsgList::PutMsg, &msgList);
    Get.join();
    Put.join();
    cout << "main end" << endl;
}

两个线程GetPut分别访问一个共享数据区,Get从共享区拿数据,Put往共享区放数据

5.2成员函数wait

this->MyCod.wait(guard, [this]() {return !this->Command.empty();});,这行代码表示,若list中没有数据则阻塞当前线程。
这行代码第一个参数是互斥量mutex,第二个参数是一个lambda表达式,用来判断list是否有数据

wait用于等一个信号,如果第二个参数返回值为false,那么wait将解锁互斥量并阻塞,即取消对临界区的占用并阻塞自己,直到有线程调用notify_one,如果其他线程notify了,当前线程唤醒,那么wait就会不断获取mutex(临界区的访问权),直到获取到,然后往下走
如果没有参数,只需要看是否有线程唤醒自己,唤醒则往下走
wait外, 条件变量还提供了wait_forwait_until,前者用于等待指定时长,后者用于等待到指定的时间。

5.3成员函数notify_one

this->MyCod.notify_one();这行代码就是在Put线程完成了数据插入后,将Get线程唤醒,只能唤醒一个线程。

5.4 存在的问题

Put线程在执行notify的时候,如果Get没有被 wait() 所阻塞,那么唤醒操作就无效,此时Put就没有将Get叫起。
当线程Put获取到的时间片比较多时,list 中积压的数据就会比较多,这个时候是否考虑要对存数据的线程进行限流呢?


六、线程返回值

6.1future和async

std::futurestd::async 创建后台任务并返回值

std::future可以放置线程执行结束返回的结果值,可以调用get成员函数获取线程的返回值
std::async与创建线程用法无异

#include<future>
int myTh5_2(){
    cout<<"mythread() start"<<" thread id="<<std::this_thread::get_id()<<endl;
    std::chrono::milliseconds milliseconds(1000);
    std::this_thread::sleep_for(milliseconds);//休息1000毫秒
    cout<<"mythread() end"<<" thread id="<<std::this_thread::get_id()<<endl;
    return 1;
}
int main(){
    cout<<"main start; id="<<std::this_thread::get_id()<<endl;
    std::future<int> result=std::async(myTh5_2);//未来的值
    cout<<"continue"<<endl;
    //get一定会等待子线程执行结束,仅能执行一次
    cout<<"return "<<result.get()<<endl;//卡住,等待myTh执行结束
    cout<<"main end"<<endl;
}

运行结果:

main start; id=140234514006400
continue
return mythread() start thread id=140234513991232
mythread() end thread id=140234513991232
1
main end

get()函数不拿到返回值,就会一直阻塞,还有wait()函数,只是会等待返回,不会获取返回值

使用std::launch::deferred延迟调用

std::future<int> result=std::async(std::launch::deferred,myTh5_2);
cout<<result.get();

当使用了deferred,只有执行了result.get()线程才会被执行

使用了deferred运行结果:
main start; id=140553532236160
continue
return mythread() start thread id=140553532236160
mythread() end thread id=140553532236160
1
main end

可以看见,没有创建主线程,入口函数是在主线程中执行
与之相对应的是std::launch::async,加入这个参数,表示线程会立即执行,而不是等到调用了get()后执行

6.2package_task

打包任务,一个类模板,参数是各种可调用对象,作用是将可调用对象包装起来,方便作为线程入口函数

用法示例:

    cout << "main start; id=" << std::this_thread::get_id() << endl;
    				//表示要求可调用对象的返回值为int,参数类型为int
    std::packaged_task<int(int)> packagedTask(myTh5_3);
    thread th(std::ref(packagedTask), 1);
    th.join();
    std::future<int> res = packagedTask.get_future();
    cout<<"res.get()="<<res.get()<<endl;

packaged_task还可以独自调用

std::packaged_task<int(int)> packagedTask1([](int a) -> int { cout << "a=" << a << endl; });
packagedTask1(5);//想当于函数调用
std::future<int> res1 = packagedTask1.get_future();
cout << "res1.get()=" << res1.get() << endl;

6.3std::promise

获取线程运行结果
通过promise保存一个值,在将来某一时刻通过future获取这个值

void myTh4(std::promise<int> &temp, int res) {
    srand((int)time(0));
    res++;
    res *= (rand()%100+1);
    std::chrono::milliseconds d(3000);
    std::this_thread::sleep_for(d);
    int result = res;
    temp.set_value(result);
}
int main() {
    std::promise<int> promise;
    thread th(myTh4, std::ref(promise), 1);//使用这个,就可以在不同的线程传参
    th.join();
    std::future<int> fu = promise.get_future();
    cout << "get res=" << fu.get() << endl;
}


//多线程应用
void myTh5(std::future<int> &f) {
    cout << "thread id=" << std::this_thread::get_id() << "\tstart" << endl;
    auto res = f.get();//等到有运算结果时才会继续
    cout << "other thread return=" << res << endl;
}

void myTh4(std::promise<int> &temp, int res) {
    srand((int) time(0));
    res++;
    res *= (rand() % 100 + 1);
    cout << "thread id=" << std::this_thread::get_id() << "正在计算。" << endl;
    std::chrono::milliseconds d(3000);
    std::this_thread::sleep_for(d);
    int result = res;
    temp.set_value(result);
}

int main() {
    std::promise<int> promise;
    std::future<int> fu = promise.get_future();
    thread th(myTh4, std::ref(promise), 1);//使用这个,就可一在不同的线程传参
    thread th1(myTh5, std::ref(fu));
    th1.join();
    th.join();
}

使用promisefuture可以实现线程之间的通信,两个线程,一个线程计算,另一个线程等待计算结束后再继续运行

运行结果:

thread id=139657918248512	start
thread id=139657926641216 正在计算。
other thread return=88

但是一个future对象只能get一次,如果有多个线程需要获取这个值,可以使用shared_future,这样就可以get多次

void myTh4(std::promise<int> &temp, int res) {
    srand((int) time(0));
    res++;
    res *= (rand() % 100 + 1);
    cout << "thread id=" << std::this_thread::get_id() << "正在计算。" << endl;
    std::chrono::milliseconds d(3000);
    std::this_thread::sleep_for(d);
    int result = res;
    temp.set_value(result);
}
void myTh6(std::shared_future<int> &f) {
    cout << "thread id=" << std::this_thread::get_id() << "\tstart" << endl;
    auto res = f.get();
    cout << "other thread return=" << res << endl;
}
void main() {
    std::promise<int> promise;
    std::shared_future<int> future = promise.get_future();
    thread th1(myTh4, std::ref(promise), 1);
    thread th3(myTh6, std::ref(future));
    thread th2(myTh6, std::ref(future));
    th1.join();
    th2.join();
    th3.join();
}

6.4future及其他
std::future<int> fu= std::async(std::launch::deferred, myTh5_2);
std::future_status status=fu.wait_for(std::chrono::seconds(1));//等待线程1秒
if(status==std::future_status::timeout)//超时  ready执行完了  deferred 延迟执行


七、原子操作

void myth6_1(int &a) {
    cout << "write start! address:" << &a << endl;
    for (size_t i = 0; i < 100000; ++i) {
        a++;
    }
}

void myth6_2(int &a) {
    cout << "read start! address:" << &a;
    for (size_t i = 0; i < 100000; ++i) {
        cout << a << endl;
    }
}

void test6_1() {
    int a = 8;
    cout << "address " << &a << "\ta=" << a << endl;
    thread th1(myth6_1, std::ref(a));
    thread th2(myth6_1, std::ref(a));
    th2.join();
    th1.join();
    cout << "address " << &a << "\ta=" << a << endl;

}

运行结果:

address 0x7fff7c796f3c	a=8
write start! address:0x7fff7c796f3c
write start! address:0x7fff7c796f3c
address 0x7fff7c796f3c	a=192558

上述代码就是两个线程,对同一个变量执行+1操作
多运行几次会发现,出现的结果可能和我们预期不一样

因为+1操作不是原子操作,线程在进行+1操作的时候,可能突然失去时间片,导致操作未完成,就造成了最终结果比预期小的现象

原子操作:不会被打断的程序执行片段
原子操作效率比互斥量更胜一筹,互斥量一般是针对某一片代码,原子操作通常针对某一变量
原子对象关键字std::atomic

void myth6_1(std::atomic<int> &a) {
    cout << "write start! address:" << &a << endl;
    for (size_t i = 0; i < 1000000; ++i) {
        a++;
    }
}
void myth6_2(std::atomic<int> &a) {
    cout << "read start! address:" << &a;
    for (size_t i = 0; i < 1000000; ++i) {
        cout << a << endl;
    }
}
void main() {
    std::atomic<int> a = 8;
    cout << "address " << &a << "\ta=" << a << endl;
    thread th1(myth6_1, std::ref(a));
    thread th2(myth6_1, std::ref(a));
    th2.join();
    th1.join();
    cout << "address " << &a << "\ta=" << a << endl;
}

将共享关键字封装为原子变量后,操作就不不会被打断,这样每次运行的结果就都正确了
一般用于统计,计数
使用原子操作实现操控线程结束

std::atomic<bool> gFlag = false;

void myth6_3() {
    std::chrono::milliseconds d(1000);
    while (!gFlag) {    //为假时不让停
        cout << "thread id = " << std::this_thread::get_id() << " running..." << endl;
        std::this_thread::sleep_for(d);//休息一秒
    }cout << "thread id = " << std::this_thread::get_id() << " stop..." << endl;
}
void main() {
    thread th1(myth6_3);
    thread th2(myth6_3);
    std::chrono::milliseconds s(5000);
    std::this_thread::sleep_for(s);
    gFlag = true;//停止两个线程
    th1.join();
    th2.join();
}

运行结果:

thread id = 140360114566720 running...
thread id = 140360106174016 running...
thread id = 140360106174016 running...
thread id = 140360114566720 running...
thread id = 140360106174016 running...
thread id = 140360114566720 running...
thread id = 140360106174016 running...
thread id = 140360114566720 running...
thread id = 140360106174016 running...
thread id = 140360114566720 running...
thread id = 140360106174016 stop...
thread id = 140360114566720 stop...

在上上个例子中,如果将原子变量的a++操作改为a=a+1,那么结果又会不符合我们预期
一般只针对++,--,+=,&=,|=,^=适用


八、async深入

上文讲过,std::launch::deferred是延迟调用,std::launch::async是强制创建一个线程

在使用std::thread()创建线程时,如果系统资源紧张,那么可能会创建失败,程序崩溃
std::async一般称之为创建一个异步任务
上文提到过,使用了deferred延迟调用后,甚至不会创建一个新的线程,只有调用了get或者wait才会开始执行入口函数
当使用sync创建任务时std::future<int> result = std::async(std::launch::deferred, myTh5_2);,若第一个参数没有指定,默认为std::launch::deferred | std::launch::async,意思就是让系统决定是异步(创建)还是同步(不创建)运行。

posted @ 2022-10-24 19:28  DaoDao777999  阅读(46)  评论(0编辑  收藏  举报