[modern c++] std::thread的使用

使用要点:

  • 如果thread对应的例程还没结束,或者thread对象当前处于joinable(待收尸)状态,此时销毁thread对象都会导致程序崩溃(确切的说是会直接终止程序)。
  • thread::join 和 thread::detach 返回后,会让 thread 进入 unjoinable(不可收尸) 状态,unjoinable(不可收尸) 状态下的 thread 可以安全销毁。
  • 使用thread默认构造函数的实例是unjoinable(不可收尸) 的。
  • 使用move以后thread会变成unjoinable(不可收尸) 。
  • detach之后,线程和 thread 对象将不再有任何关系,此时我们可以随意销毁thread,当然,我们也永远无法 “触碰到” 线程,因为他已经由系统接管。
  • thread 是 movable 的,但不是 copyable 的。对已经包含一个线程的thread再次使用 std::move 进行赋值会导致程序退出,即不要重复给thread move赋值
  • 对 unjoinable(不可收尸) 的 thread 对象使用 join() 会抛异常,因此再使用 join() 之前要先通过 joinable() 判断下。

小结:要么等待例程结束,要么在即将销毁thread实例之前判断joinable状态并调用join或detach,要么这个thread实例是默认构造出来的,要么已经调用move把线程转移给其他thread实例了,否则任何形式的thread实例销毁都会导致程序崩溃。另外,join会阻塞,detach不会阻塞。

用法一:使用可调用对象


#include <iostream>
#include <thread>
#include <Windows.h>

using namespace std;

template <typename T>
class ThreadRoutine
{
public:
	ThreadRoutine() = delete;	//T 类型位置不考虑T进行默认构造,简单起见直接禁用默认构造
	//ThreadRoutine(const ThreadRoutine&) = delete;	//禁用拷贝构造 (放开编译会报错,为什么???)
	ThreadRoutine& operator=(const ThreadRoutine& ins) = delete;	//禁用拷贝赋值
	ThreadRoutine(T input) {
		_Input = input;		//T 必须支持拷贝构造和拷贝赋值
	}

	void operator()() 
	{
		cout << "_Input's size is -> " << sizeof(T) << endl;
	}

	T _Input;
	//T & _R_Input;	//如果使用引用则需要考虑内存合法性问题,因为传入的参数可能在线程还没有执行时就已经被释放,所以建议使用拷贝值的方式传参,或者使用 堆内容,然后使用智能指针进行控制
};

int main()
{
	ThreadRoutine<int> t(10);
	thread routine(t);
	routine.join();

    std::cout << "Hello World!\n";
	while (1) Sleep(1000);
}

用法二:使用函数


#include <iostream>
#include <thread>
#include <memory>
#include <Windows.h>

using namespace std;

struct RoutineInput {
	char c;
	int i;
	long l;
	void * data;
};

void Routine(shared_ptr<RoutineInput> input) {
	cout << "sizeof intput -> " << sizeof(*input) << endl;
}

int main()
{
	std::shared_ptr<RoutineInput> input = make_shared<RoutineInput>();	//使用智能指针防止routine中访问已被释放的内存
	thread routine(Routine, input);
	routine.join();

    std::cout << "Hello World!\n";
	while (1) Sleep(1000);
}

需要注意:

1)使用这种方法时,传递给函数的参数是传值的(即便函数入参明确表示是引用类型也不生效,这很重要),如果想传字符串而入参是字符数组,那么请务必把字符数组显式转换为std::string,因为std::string底层实现是计数模式,所以在发现引用即将为0时会进行一次 copy-on-write 创建一个新的副本。但是作为C/C++开发者,心里要有数,尽量避免危险的指针使用场景。

2)上面提到了只会传值,那么如果确实想传引用怎么办? 答案是使用 std::ref 进行强制转换

小技巧:

前面提到了thread必须在unjoinable的状态下才可以被销毁,否则会导致程序终止。那么我们可以使用 RAII 策略加保险。

Demo:

class thread_guard
{
	std::thread& t;
public:
	explicit thread_guard(std::thread& t_) :
		t(t_)
	{}
	~thread_guard()
	{
		if (t.joinable())
		{
			t.join();
            //t.detach();   //如果不希望阻塞就使用detach,否则使用join等待线程结束
		}
	}
	thread_guard(thread_guard const&) = delete;
	thread_guard& operator=(thread_guard const&) = delete;
};
struct func;
void f()
{
	int some_local_state = 0;
	func my_func(some_local_state);
	std::thread t(my_func);
	thread_guard g(t);
	do_something_in_current_thread();
}

这里利用的是 “函数调用栈释放是从栈顶向栈底的方向” 这一策略, thread_guard 局部变量在 thread 局部变量之后定义,那么在释放栈的时候 thread_guard 的析构函数会 早于 thread 实例销毁。因此可以在析构函数中转变 thread 为 unjoinable。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值