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

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

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

OceanStar的学习笔记博主-博客

NGC_2070博主-博客

文章目录

    • std::thread
      • thread的提出
      • 使用方法、语法
    • join、detach
      • 线程安全问题
        • mutex的提出
        • 最优的使用方法:std::lock_guard
    • std::async
      • std::async的提出
      • 使用方法、语法
    • 二者的使用区别

std::thread

C++官方链接

thread的提出

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

看一个例子:

#include 
#include 
 
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 explicit thread(Fn&& fn, Args&&… args) 初始化构造函数 创建一个线程,以args为参数
~thread() 析构函数 析构对象

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

// Compiler: MSVC 19.29.30038.1
// C++ Standard: C++17
#include 
#include 
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
#include

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
#include
#include

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
#include
#include

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调用方法很容易拿到线程入口函数的返回值。

你可能感兴趣的:(C++,c++,算法,开发语言)