C++——std::async和std::thread

作者:小 琛
欢迎转载,请标明出处

参考文章:
傻月菇凉博主-博客

OceanStar的学习笔记博主-博客

NGC_2070博主-博客

std::thread

C++官方链接

thread的提出

C++11提出了线程相关的内容,我们可以直接用它来实现线程操作

看一个例子:

#include <iostream>
#include <thread>
 
void test()
{
	std::cout << "hello world!" << std::endl;
}
 
int main()
{
	//hello函数会在新的线程中执行
	std::thread t(test);
	//join会在调用线程等待std::thread对象相关联的线程结束 
	t.join();
 
	system("pause"); //msvc
	return 0;
}

使用方法、语法

函数类别作用
thread() noexcept默认构造函数创建一个线程
template <class Fn, class… Args>explicit thread(Fn&& fn, Args&&… args)初始化构造函数创建一个线程,以args为参数
~thread()析构函数析构对象

一般来说,我们使用thread去创建一个线程,直接调用接口即可,再将希望执行的异步函数传入即可;而对于执行异步函数的参数,可以直接在创建时传入。注意,一定要保证传入的参数和执行异步函数的参数对应,否则会编译错误
例如下面的例子:

// Compiler: MSVC 19.29.30038.1
// C++ Standard: C++17
#include <iostream>
#include <thread>
using namespace std;
void countnumber(int id, unsigned int n) {
	for (unsigned int i = 1; i <= n; i++);
	cout << "Thread " << id << " finished!" << endl;
}
int main() {
	thread th[10];
	for (int i = 0; i < 10; i++)
		th[i] = thread(countnumber, i, 100000000);
	for (int i = 0; i < 10; i++)
		th[i].join();
	return 0;
}

传入的函数支持函数指针、lambda表达式

join、detach

但我们执行下面的代码,会发生编译错误:

#include<iostream>
#include<thread>

using namespace std;

void thread1() {
    for(int i=0;i<20;++i)
        cout << "thread1..." << endl;
}

void thread2() {
    for (int i = 0; i<20; ++i)
        cout << "thread2..." << endl;
}

int main(int argc, char* argv[]) {
    thread th1(thread1);   //实例化一个线程对象th1,该线程开始执行
    thread th2(thread2);
    cout << "main..." << endl;
    return 0;
}

原因:main函数在创建线程后,继续执行,并retrun 0即结束,但异步的线程并没有结束而引发异常。

熟悉线程相关知识的同学都知道,在创建线程后,要对它“负责到底”

  • thread::join():让主线程等待直到该子线程执行结束。注意,这个接口调用后,线程属性就从joinable变为join,同时已经执行结束的线程是不能被joinable的,所以规范的操作应当是,调用接口前先调用joinable()接口来判断是否可以执行join
  • thread::detach:将当前线程对象所代表的执行实例与该线程对象分离,使得线程的执行可以单独进行。一旦线程执行完毕,它所分配的资源将会被释放。一定要注意,分离后,有可能主线程执行结束并已经将相关资源释放,而子线程仍在继续执行,因此如果你的程序不能保证子线程一定在主线程执行后结束,请慎用

线程安全问题

mutex的提出

涉及线程,线程安全是一定提出的话题,关于线程安全不在该文章赘述。C++11提出了mutex相关内容,使用要包含头文件《mutex》

举个例子:

#include<iostream>
#include<thread>
#include<mutex>

using namespace std;

mutex m;
int cnt = 10;

void thread1() {
    while (cnt > 5){
        m.lock();
        if (cnt > 0) {
            --cnt;
            cout << cnt << endl;
        }
        m.unlock();
    }
}

void thread2() {
    while (cnt > 0) {
        m.lock();
        if (cnt > 0) {
            cnt -= 10;
            cout << cnt << endl;
        }
        m.unlock();
    }
}

int main(int argc, char* argv[]) {
    thread th1(thread1);   //实例化一个线程对象th1,该线程开始执行
    thread th2(thread2);
    th1.join();
    th2.join();
    cout << "main..." << endl;
    return 0;
}
最优的使用方法:std::lock_guard

提起线程会引发线程安全,而提出锁则一定引发死锁相关内容,为了避免该问题发生,C++提出了std::lock_guard,利用RAII思想来规避

使用方法很简单,在需要上锁的地方,将声明的mutex锁交给std::lock_guard管理,它的构造会自动帮助你上锁,而在代码执行完后即对象析构时,会自动解锁,避免了死锁的出现,在实际开发中,应多用这种方式。

看个例子:

#include<iostream>
#include<thread>
#include<mutex>

using namespace std;

mutex m;
int cnt = 10;

void thread1() {
    while (cnt > 5){
        lock_guard<mutex> lockGuard(m);
        if (cnt > 0) {
            --cnt;
            cout << cnt << endl;
        }
    }
}

void thread2() {
    while (cnt > 0) {
        lock_guard<mutex> lockGuard(m);
        if (cnt > 0) {
            cnt -= 10;
            cout << cnt << endl;
        }
    }
}

int main(int argc, char* argv[]) {
    thread th1(thread1);   //实例化一个线程对象th1,该线程开始执行
    thread th2(thread2);
    th1.join();
    th2.join();
    cout << "main..." << endl;
    return 0;
}

std::async

std::async的提出

主要为了解决thread相关的以下几点:

std::async可以直接拿到线程执行结果;
std::async可以避免线程创建失败的情况;
std::async可以手动触发;

使用方法、语法

原型:

template<class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type> async(launch policy, Fn&& fn, Args&&...args);

std::async中的第一个参数是启动策略,它控制std::async的异步行为,我们可以用三种不同的启动策略来创建std::async

  • ·std::launch::async
    保证异步行为,即传递函数将在单独的线程中执行
  • std::launch::deferred
    当其他线程调用get()来访问共享状态时,将调用非异步行为
  • std::launch::async | std::launch::deferred
    默认行为。有了这个启动策略,它可以异步运行或不运行,这取决于系统的负载,但我们无法控制它。

通常情况下,我们使用get配合异步操作,看下面的伪代码

std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
 
	//从文件获取数据
	std::future<std::string> fileData = std::async(std::launch::deferred, fetchDataFromFile, "Data");
 
	//直到调用get函数fetchDataFromFile才开始执行
	std::string FileData = fileData.get();
	//如果fetchDataFromDB()执行没有完成,get会一直阻塞当前线程
	std::string dbData = resultFromDB.get();

区别在于:get()+std::launch::async(异步),调用get时异步开始进行,并且不会影响当前线程;get()+std::launch::deferred,当该异步函数没有执行结束,get会一直阻塞当前线程

std::async和std::thread最明显的不同,就是async有时候并不创建新线程。

  • 如果你用std::launch::deferred来调用async会怎么样?
    std::launch::deferred延迟调用,并且不创建新线程,延迟到future对象调用 get()或者 wait()
    的时候才执行mythread()。 如果没有调用get或者wait,那么这个mythread()不会执行。
  • std::launch::async:强制这个异步任务在新线程上执行,这 意味着,系统必须要给我创建出新线程来运行mythread()。
  • std::launch::async | std::launch::deferred,这里这个 | :意味着调用async的行为可能是
    “ 创建新线程并立即执行” 或者 没有创建新线程并且延迟到调用 result.get()才开始执行任务入口函数,两者居其一。
  • 不带额外参数,只给async函数一个 入口函数名: 默认值应该是std::launch::async |
    std::launch::deferred;和c)效果完全一致。
    换句话说:系统会自行决定是异步(创建新线程)还是同步(不创建新线程)方式运行。

二者的使用区别

std::thread创建线程,如果系统资源紧张,创建线程失败,那么整个程序就会报异常崩溃(有脾气)。
std::thread创建线程的方式,如果线程返回值,你想拿到这个值也不容易

std::async创建异步任务。可能创建也可能不创建线程。
std::async调用方法很容易拿到线程入口函数的返回值。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,C++ 11 引入了 std::async 函数,可以用它来创建异步任务。std::async 函数的原型如下所示: ```c++ template <class Function, class... Args> std::future<typename std::result_of<Function(Args...)>::type> async(Function&& f, Args&&... args); ``` std::async 函数的作用是创建一个异步任务,并返回一个 std::future 对象,用于获取异步任务的返回值。std::async 函数的第一个参数是要执行的函数,后面的参数是该函数的参数。该函数会在一个新线程中执行,并在执行完成后返回结果。如果该函数抛出异常,则 std::future 对象中会保存该异常信息。 std::async 函数还可以指定执行策略,例如 std::launch::async 表示在新线程中执行异步任务,std::launch::deferred 表示在调用 std::future::get() 函数时执行异步任务。如果不指定执行策略,则由编译器自行决定。 以下是一个使用 std::async 函数创建异步任务的示例: ```c++ #include <iostream> #include <future> int foo(int x) { std::cout << "foo is running in thread " << std::this_thread::get_id() << std::endl; return x + 1; } int main() { std::future<int> result = std::async(std::launch::async, foo, 1); std::cout << "main is running in thread " << std::this_thread::get_id() << std::endl; std::cout << "result is " << result.get() << std::endl; return 0; } ``` 在上面的示例中,使用 std::async 函数创建了一个异步任务 foo,并将参数 1 传递给该函数。执行结果会保存在 std::future 对象中,可以使用 std::future::get() 函数获取异步任务的返回值。在主线程中也输出了一些信息,用于对比异步任务和主线程中的执行情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值