C++11多线程笔记二(创建线程)

本文详细介绍了如何使用std::thread创建线程,包括使用函数、类成员函数以及lambda表达式作为线程执行体,并讨论了参数传递、join()和detach()的使用,强调了线程安全和资源管理的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、启创建线程

std::thread接受可调用对象创建线程

1. 使用函数作为线程的执行体

void myFunction()
{
}
std::thread t1(myFunction);
//带有参数的函数调用:
void myFunction2(int&& a)
{
	std::cout << "a的值为:" << a << std::endl; 
}
int i = 10;
std::thread t2(myFunction2, std::move(i));

thread在传递参数时,是以右值传递的:

template <class Fn, class... Args>
explicit thread(Fn&& fn, Args&&... args)

如果想要传入左值呢?
使用函数作为线程的执行体时,若传入参数为左值引用,需要使用std::ref。

如果不使用std::ref,而是直接将 a 传递给 std::thread,则会发生参数类型的改变,导致编译错误,这是因为 std::thread 的构造函数会复制参数,并且复制引用类型的参数会导致编译错误

std::ref 是一个包装器,它会将参数转换为引用类型,以便在std::thread的构造函数中正确传递引用参数,避免在将参数传递给模板函数或函数对象时丢失参数的引用性质

void myFunction()
{
}
std::thread t1(myFunction);
//带有参数的函数调用:
void myFunction2(int& a)
{
	std::cout << "a的值为:" << a << std::endl; 
}
int i = 10;
std::thread t2(myFunction2, std::ref(i));

2. 使用类的成员函数作为线程的执行体

class TA
{
public:
	TA(int &&i):m_i(i)
	{
		cout << "TA()构造函数被执行" << endl;
	}
	TA(const TA &ta) :m_i(ta.m_i)
	{
		cout << "TA()拷贝构造函数被执行" << endl;
	}
	~TA()
	{
		cout << "~TA()析构函数被执行" << endl;
	}
	//仿函数,使用重载函数operator()
	void operator()()
	{
		cout << "m_i的值为:" << m_i << endl;
	}
	void myMethod(int &i) {
		cout << "myMethod中m_i的值为:" << m_i << endl;
		cout << "myMethod中i的值为:" << i << endl;
	}
private:
	int m_i;
};

int main()
{
	TA obj(10);
	thread mythread(obj);   //调用拷贝构造函数,实际上是复制到线程中去的
	int paramValue = 42;
	//带有参数的线程函数调用
	thread myta(&TA::myMethod, &obj, ref(paramValue));
	//ref:将传入的对象包装为引用类型的包装器,并提供一个类似引用的接口。
	//这样可以在使用包装器时模拟引用传递的效果。
	myta.join();
	mythread.join();
	return 0;
}

3. 使用lmbda表达式作为线程的执行体

  • 无参数函数:
void myMethod()
{
	cout << "开启myMethod函数线程" << endl;
}


int main()
{
	std::thread t1([]() {
		myMethod();
	});
	t1.join();
	return 0;
}
  • 带参数函数
void myMethod(int& a)
{
	cout << "a的值为:" << a << endl;
}


int main()
{
	int a = 10;
	std::thread t1([&]() {
		myMethod(a);
	});
	t1.join();
	return 0;
}
  • 调用仿函数
class TA
{
public:
	TA(int &i):m_i(i){}
	void operator()()
	{
		cout << "m_i的值为:" << m_i << endl;
	}
private:
	int m_i;
};

int main()
{
	int value = 10;
	std::thread t1([&]() {
		TA obj1(value);
		obj1();
	});
	t1.join();
	return 0;
}
  • 调用类成员函数
class TA
{
public:
	TA(int &i):m_i(i){}
	void operator()()
	{
		cout << "m_i的值为:" << m_i << endl;
	}
	void myMethod(int &m) {
		cout << "myMethod中m_i的值为:" << m_i << endl;
		cout << "myMethod中m的值为:" << m << endl;
	}
private:
	int m_i;
};

int main()
{
	int value = 10;
	int paramValue = 40;
	TA obj(value);
	std::thread t1([&]() {
		obj.myMethod(paramValue);
	});
	t1.join();
	return 0;
}

二、join()detach()

std::thread 类提供了 join()detach() 两个成员函数,用于控制线程的行为。

1. join()

  • join() 函数用于等待线程的完成。
  • 调用 join() 会阻塞当前线程,直到被调用的线程执行完毕。也就是说,join()会将主线程挂起,直到被调用的线程执行完毕,主线程才会继续执行后面的代码。
  • 使用 join()可以保证在主线程退出之前,子线程的执行完毕,避免了子线程被强制终止。

2. detach()

  • detach() 函数用于分离线程,使得目标线程成为守护线程。调用detach()后,被调用的线程与主线程分离,它们可以并发执行。分离的线程在执行结束后会自动清理资源,不需要主线程进行等待。
  • 一旦线程被分离,无法再与之通信,无法再使用join()等待其完成。

3. 必须使用 join()detach() 进行处理

对于一个 std::thread 对象,必须使用 join()detach() 进行处理,否则在 std::thread 对象析构时会触发 std::terminate() 终止程序的执行。这是因为 std::thread 对象在析构时要检查线程的状态,如果线程还在运行且未被 join()detach() 处理,那么就会引发终止。

4. joinable()

joinable()std::thread 类的成员函数,用于检查线程是否可以被 join()detach()。它返回一个布尔值,指示线程是否可被加入(joinable)。

  • 如果 joinable() 返回 true,表示线程可以被 join() 或 detach(),即线程处于活动状态,尚未被加入或分离。
  • 如果 joinable() 返回 false,表示线程不可被 join() 或detach(),即线程已经被加入或分离,或者是默认构造的空线程对象。

使用 joinable() 函数在编程中进行线程状态的检查,以避免对不可被加入的线程进行错误的操作。通常在使用 join() 或 detach() 之前,都会先检查线程是否可被加入,以确保正确处理线程对象。

std::thread t(myFunction);  // 创建线程
if (t.joinable()) {
    t.join();  // 加入线程
}

5. 使用detach()注意事项

  • 若传递int这种简单类型参数,建议都是值传递,不要引用,防止局部变量失效导致线程对内存的非法引用
  • 若传递类对象,避免隐式类型转换,全部都在创建线程传参时就构建出临时对象来,然后在函数参数里用引用来接,否则系统会再多构造一次对象。
  • 建议不适用detach()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值