std::thread介绍

目录

    • 创建线程
    • 成员函数使用
    • 不写`join()`或`detach()`报错
    • mutex
      • 基本使用
      • 进阶
    • 参考

  C++中的一个表示执行线程的类。一个执行线程是一个指令序列,它可以在多线程环境中与其它这样的序列同时执行,共享相同的地址空间。一个已经初始化了的线程对象表示正在执行的活动线程,这样的线程对象是joinable(可接合的,不知道翻译的对不对),并且具有唯一的线程id。一个默认构造的(即未初始化的)线程对象是not joinable(不可接合的),其线程id对所有不可接合的线程都是通用的。如果从可接合线程中 移动,或者对其 调用join()或detach(),则可接合线程将变成不可接合的。
  下面以表格的形式介绍了该类的成员变量、成员函数和非成员重载。

成员变量 介绍
id 线程id
native_handle_type 本地句柄类型
成员函数 介绍
(constructor) 构造线程
(destructor) 构析线程
operator= move-assign thread
get_id 获得线程id
joinable 检查是否可接合的
join 加入线程
detach 分离线程
swap 交换线程
native_handle 获得本地句柄
hardware_concurrency 检测硬件并发性
非成员重载 介绍
swap(thread) 交换线程

创建线程

  线程的创建方法有4种,分别介绍如下。

(1)通过函数指针创建

//1 对于无参数的函数
void func() {}
thread th(func);

//2 对于按值传递参数的函数
void func(int a, int b) {}
thread th(func, 1, 2);

//3 对于按址传递参数的函数
void func(int& a, int& b) {}
int val_a = 1, val_b = 2;
thread th(func, ref(val_a), ref(val_b));

//4 对于按值和按址混合传递参数的函数
void func(int a, int b, int& c) {}
int val_c = 3;
thread th(func, 1, 2, ref(val_c));

(2)通过函数对象创建

#include 
#include 

using namespace std;

class funcObject
{
public:
	funcObject(int a, int b) : a_(a), b_(b) {}
	void operator()() const
	{
		cout << "a_ = " << a_ << ", b_ = " << b_ << endl;
	}
private:
	int a_;
	int b_;
};

int main()
{
	//使用函数对象创建方法1
	thread t1{ funcObject{1, 11} };
	t1.join();

	//使用函数对象创建方法2
	thread t2(funcObject(2, 22));
	t2.join();

	//使用函数对象创建方法3
	funcObject fo(3, 33);
	thread t3(fo);
	t3.join();

	return 0;
}

(3)通过lambda表示式创建

int a = 1, b = 2;
thread t( [a, b]
{
	cout << "a = " << a << ", b = " << b << endl;
} 
);

(4)通过成员函数创建

#include 
#include 

using namespace std;

class MyClass
{
public:
	MyClass(int a, int b) : a_(a), b_(b) {}
	void func() {cout << "a_ = " << a_ << ", b_ = " << b_ << endl;}
private:
	int a_;
	int b_;	
};

int main()
{
	MyClass mc(1, 2);
	thread t(&MyClass::func, &mc);  //通过成员函数来创建线程
	t.join();
	return 0;
}

成员函数使用

(1)join()

当使用join()函数时,主线程main()被阻塞,等待被调线程th终止,然后主线程main()回收被调线程th资源,并继续运行。演示代码如下,

#include 
#include 

using namespace std;

void func1() 
{
	int n = 10;
	while(n--)
		cout << "executing func1()..." << endl; 
	cout << "fun1() end!" << endl;
}

int main()
{
	thread th(func1);  //实例化一个线程对象th,该线程开始执行
	th.join();  //如果缺少该语句,则主线程会直接结束,即便子线程th没有执行完!
	cout << "main() end!" << endl;
	return 0;
}

输出结果为,

executing func1()...
executing func1()...
executing func1()...
executing func1()...
executing func1()...
executing func1()...
executing func1()...
executing func1()...
executing func1()...
executing func1()...
fun1() end!
main() end!

(2)detach()

使用detach()函数会使得线程th在后台运行,并且主线程main()不必等待子线程th执行完后才可以退出,还有,即便主线程main()退出之后,子线程th仍旧可在后台运行。示例代码如下,

#include 
#include 

using namespace std;

void func() 
{
	while(1)
		cout << "func1 is operating a" << endl;
}

int main()
{
	thread th(func);  //实例化一个线程对象th,该线程开始执行
	th.detach();
	cout << "main end()!" << endl;
	return 0;
}

程序运行结果为,

func1 is operating amain end()!

(3)joinable()

用来判断线程是否可以成功使用join()detach(),返回truefalse。在join()detach()调用之前,返回true;在join()detach()调用之后返回false。示例代码如下,

#include 
#include 

using namespace std;

void func() 
{
	int n = 30;
	while(n--)
		cout << "func1 is operating a" << endl;
}

int main()
{
	thread th(func);  //实例化一个线程对象th,该线程开始执行
	cout << "th.joinable() = " << th.joinable() << endl;
	th.join(); //或可以写成th.detach();
	cout << "th.joinable() = " << th.joinable() << endl;
	cout << "main end()!" << endl;
	return 0;
}

程序运行结果为,

func1 is operating ath.joinable() = 1

func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
func1 is operating a
th.joinable() = 0
main end()!

(4)get_id()

返回线程id,注意如果返回值为0表示线程对象不可接合。示例代码如下,

#include 
#include 

using namespace std;

void func1() {}

void func2() {}


int main()
{
	thread th1(func1);  //实例化一个线程对象th1,该线程开始执行
	thread th2(func2);  //实例化一个线程对象th2,该线程开始执行
	cout << "线程th1的id为:" << th1.get_id() << ",线程th2的id为:" << th2.get_id() << endl;
	th1.join();
	th2.join();
	cout << "线程th1的id为:" << th1.get_id() << ",线程th2的id为:" << th2.get_id() << endl;
	cout << "main end()!" << endl;
	return 0;
}

程序运行结果为,

线程th1的id为:23720,线程th2的id为:24636
线程th1的id为:0,线程th2的id为:0
main end()!

(5)swap(·) or swap(·,·)

  什么情况下会使用到交换线程呢?需要对线程进行排序操作时。
  使用方式为

  1. th1.swap(th2);,表示线程th1和线程th2交换。该函数为类的成员函数。
  2. std::swap(th1, th2),表示线程th1和线程th2交换。该函数为非成员函数重载。

  注意,使用swap()函数之前,必须保证线程对象是可接合的,即joinable() == true,否则会报错!
  示例代码如下,

#include 
#include 

using namespace std;

void func1() {}
void func2() {}

int main()
{
	thread th1(func1);
	thread th2(func2);

	cout << "th1 id = " << th1.get_id() << "; th2 id = " << th2.get_id() << "!" << endl;

	th1.swap(th2);

	cout << "th1 id = " << th1.get_id() << "; th2 id = " << th2.get_id() << "!" << endl;

	swap(th1, th2);
	cout << "th1 id = " << th1.get_id() << "; th2 id = " << th2.get_id() << "!" << endl;

	th1.join();
	th2.join();

	return 0;
}

程序输出结果为,

th1 id = 9208; th2 id = 14400!
th1 id = 14400; th2 id = 9208!
th1 id = 9208; th2 id = 14400!

不写join()detach()报错

#include 
#include 

using namespace std;

void func()
{
	cout << "func() is executed!" << endl;
}

int main()
{
	thread th(func);
	th.join();  //如果注释该行,程序会报错
	return 0;
}

原因是什么呢?这个问题与thread类的构析函数(相关介绍见请点击)有关。thread类的析构函数中,有如下代码块

~thread() noexcept
{	// clean up
	if (joinable())
		_STD terminate();
}

也就是说如果线程对象是可接合的,即joinable() == true,那么将会调用std::terminate()函数,程序报错。
  只有当线程对象是不可接合的,即joinable() == false,也就是说,

  • it was default-constructed
  • it was moved from
  • join() has been called
  • detach() has been called

那么该线程对象才可以被安全销毁,也即析构函数可以被成功执行。

mutex

基本使用

  互斥量,需要包含头文件#include,作用是防止不同的线程同时操作同一个共享数据。另一点就是使得线程的执行有了先后顺序,必须等待线程th1结束之后,线程th2才会运行。
  考虑这样一个问题:有全局变量a,函数void func1()和函数void func2()都需要使用变量a,在主线程中main()中启用线程th1执行函数func1,启用线程th2执行函数func2分析:由于函数func1和函数func2运行在不同线程中的,所以可能会出现两者同时对变量a进行操作的情况,因此,需要定义一个互斥量变量m,即mutex m;,在函数func1和函数func2的开始时上锁m.lock()和结尾时解锁m.unlock()。示例代码如下,

#include 
#include 
#include 

using namespace std;

int a;
mutex m;

void func1() 
{
	m.lock();
	cout << "operating a" << endl;
	m.unlock();
}

void func2()
{
	m.lock();
	cout << "operating a" << endl;
	m.unlock();
}


int main()
{
	thread th1(func1);  //实例化一个线程对象th1,该线程开始执行
	thread th2(func2);  //实例化一个线程对象th2,该线程开始执行
	th1.join();
	th2.join();
	cout << "main end()!" << endl;
	return 0;
}

程序运行结果为,

operating a
operating a
main end()!

进阶

  使用m.lock()m.unlock()来保证数据不会被同时访问,这种方法有一定的缺陷。比如,如果在func1函数中m.lock()之后函数异常退出,那么由于没有对互斥量m解锁(即,m.unlock()),func2函数将无法访问变量a,线程th2被堵塞。如下异常代码所示,

#include 
#include 
#include 

using namespace std;

int a;
mutex m;

void func1() 
{
	m.lock();
	cout << "operating a" << endl;
	return;  //func1函数在m.unlock()之前退出
	m.unlock();
}

void func2()
{
	m.lock();  //线程th2被阻塞
	cout << "operating a" << endl;
	m.unlock();
}


int main()
{
	thread th1(func1);  //实例化一个线程对象th1,该线程开始执行
	thread th2(func2);  //实例化一个线程对象th2,该线程开始执行
	th1.join();
	th2.join();
	cout << "main end()!" << endl;
	return 0;
}

  因此,一般使用lock_guard l(m);unique_lock l(m);来代替m.lock(); m.unlock();

#include 
#include 
#include 

using namespace std;

int a;
mutex m;

void func1() 
{
	lock_guard<mutex> l(m);  //或写成unique_lock l(m);
	cout << "operating a" << endl;
	return;  
}

void func2()
{
	lock_guard<mutex> l(m);  //或写成unique_lock l(m);
	cout << "operating a" << endl;
}


int main()
{
	thread th1(func1);  //实例化一个线程对象th1,该线程开始执行
	thread th2(func2);  //实例化一个线程对象th2,该线程开始执行
	th1.join();
	th2.join();
	cout << "main end()!" << endl;
	return 0;
}

程序输出为,

operating a
operating a
main end()!

参考

  1. http://www.cplusplus.com/reference/thread/thread/?kw=thread
  2. https://en.cppreference.com/w/Main_Page
  3. https://blog.csdn.net/weixin_40087851/article/details/82685510?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
  4. https://blog.csdn.net/myruo/article/details/89370445
  5. https://blog.csdn.net/me1171115772/article/details/106952308
  6. https://blog.csdn.net/weixin_44862644/article/details/115765250
  7. https://blog.csdn.net/qq_36784975/article/details/87699113
  8. https://blog.csdn.net/wxc971231/article/details/105979443

你可能感兴趣的:(C++学习,thread用法)