一.范例演示线程运行的开始和结束
程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;
实际上这个是主线程在执行,主线程从main函函数返回,则整个进程执行完毕。
主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕就代表这个线程结束。
整个进程是否执行完毕的标志是 主线程是否执行完,如果主线程执行完毕了,就代表整个进程执行完毕了;
此时,一般情况下:如果其他子线程还没有执行完毕,那么这些子线程也会被操作系统强行终止。
所以,一般情况下,如果想保持子线程(自己用代码创建的线程)的运行状态的话,那么要让主线程一直保持运行或者在子线程结束后结束。
二.创建多线程
使用 std::thread 创建子线程非常直接。最基本的方法是:构造一个 std::thread 对象,并传入一个可调用对象(函数、lambda、函数对象)作为线程的入口点。
join()函数
阻塞主线程,让主线程等待子线程执行完毕后再继续往下执行。
detach()函数
分离主线程。如果不关心子线程何时结束,且子线程不会访问主线程中即将销毁的资源,可以将其分离 。分离后,子线程在后台独立运行,主线程不再能控制它
谨慎使用,分离的线程可能在主线程退出后仍在运行,访问已销毁的变量会导致未定义行为。
如果thread对象销毁时仍未join()或detach(),程序会崩溃。
头文件
cpp
#include<iostream>
#include<thread> //thread的头文件
using namespace std;
-
无参函数
thread 对象名(函数名);cppvoid print() { cout << "子线程开始" << endl; //...中间代码 cout << "子线程结束" << endl; } //用print构造thread对象 thread pt(print); //创建子线程,线程起点(入口)是print(),创建后线程开始执行 pt.join(); //阻塞主线程,让主线程等待子线程执行完毕。 //子线程执行完毕后join()就执行完毕,主线程继续向下执行 -
按值传递(默认模式)
thread 对象名(函数名,参数1,参数2,...);cppvoid print_sum(int a, int b) { //需要传递参数的函数,默认按值传递 std::cout << "Sum = " << a + b << std::endl; } thread t(print_sum, 10, 20); // 参数直接跟在函数名后 t.join(); -
引用传递(修改值)
thread 对象名(函数名,ref(参数1),ref(参数2),...); 不加ref会报错cppvoid modify(int& x) { //传递引用 x = 100; } int val = 0; thread m(modify, ref(val)); // 不加 ref 会编译错误 m.join(); cout << val << endl; // 输出 100 -
lambda表达式
thread 对象名([捕获参数](){函数体});cppint local = 42; thread l([&local]() { local += 1; cout << "Inside thread: " << local << endl; }); l.join(); cout << "Outside: " << local << endl; -
使用成员函数作为入口需要传入对象指针和成员函数指针
thread 对象名(成员函数指针,对象指针,参数1,参数2,...);cppclass MyClass { public: MyClass(){ cout<<"子线程开始"<<endl; //... cout<<"子线程结束"<<endl; } void work(int n) { cout << "Working on " << n << endl; } }; MyClass c; thread cla(&MyClass::work , &c, 10); cla.join(); MyClass d; thread obj(d); obj.join();
三.线程的移动语义
thread 是可移动但不可复制的。可以将线程的所有权转移给另一个 thread 对象:
cpp
thread t1(hello);
thread t2 = move(t1); // t1 不再代表任何线程
t2.join();