C++并发(三)线程所有权的转让

93 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1、线程所有权的转让

有时候我们需要做这样一个操作,就是将这个线程返回,转换线程的所有权去调用这个函数,这个在C++中就叫做转移线程所有权。我们以一个例子来说明这个问题。

1.1 线程实例之间的转移

void someThing() {
	cout << "doing something" << endl;
}

int main() {
	std::thread t1(someThing);
	thread::id index = t1.get_id();
	cout << "转移前的线程ID: "<< index << endl;

	std::thread t2 = std::move(t1);
	thread::id index2 = t1.get_id();
	cout << "转移后的线程ID: " << index2 << endl;

	thread::id index3 = t2.get_id();
	cout << "转移后新线程ID: " << index3 << endl;
	t2.join();
	return 0;
}

image.png 从上面的日志可以看到,线程ID没有变化,但是已经转移了,通过move的方法 我们把线程t1的占用转移到了线程t2的身上。这样执行完成后,t1就和线程没有关联了。

1.2 函数对象返回 std::thread

void do_someThing() {
	cout << "do something" << endl;
};
std::thread f() {
	void do_someThing();
	return std::thread(do_someThing);
}
int main() {
	std::thread t(f());
	t.join();
	return 0;
}

image.png 如上图说是通过函数返回在内部进行所有权转换,这里并没有使用std::move

1.3 函数参数 std::thread

从上面的例子可以看到,线程的所有权可以通过函数返回形式在内部传递,那么可以推出,线程肯定也是可以通过函数的参数进行传递所有权,请看下面的例子

void testThread(std::thread localT) {
	localT.join();
}
int main() {
	std::thread thread1(someThing);
	testThread(std::move(thread1));
	return 0;
}

image.png

1.4 通过类封装线程

在这里我们通过一个类,来管理,下面我们逐步分析这个封装的思想

void someThing() {
	cout << "hello" << endl;
}
struct func {

	int& i;
	func(int& i_) :i(i_) {
	
	}

	void operator()() {
		someThing();
		//for (unsigned j = 0; j < 1000000; ++j)
		//{
		//	someThing();
		//}
	}

};

class Scoped_thread {
	std::thread t;
public:
	explicit Scoped_thread(std::thread t_) :t(std::move(t_)) {
		cout << "够造函数" << endl;
		if (!t.joinable()) {
			throw std::logic_error("No thread");
		}
	}
	explicit Scoped_thread() {
		cout << "hel" << endl;
	}
	/*在释放前调用.join*/
	~Scoped_thread() {
		cout << "析构函数" << endl;
		t.join();
	}
	Scoped_thread(Scoped_thread const&) = delete;
	Scoped_thread& operator=(Scoped_thread const&) = delete;
};

void oops() {
	int local_var = 1;

	Scoped_thread t(std::move(std::thread(func(local_var))));

	cout << "执行完毕" << endl;
}
int main() {

	oops();
	return 0;
}

image.png 1、someThing 作为线程的具体执行的方法

2、struct func 作为一个线程函数

3、Scoped_thread作为封装的主体类 在这个类的构造函数中,我们将外部的线程传递过来,并复制到类中的线程t,在构造函数中判断线程是否存在,通过joinable来实现

4、在析构函数中调用join()

5、Scoped_thread t(std::move(std::thread(func(local_var))));在外部使用move转移线程的所有权给内部线程

1.5 批量线程的处理

在很多场景,我们经常需要创建很多线程,然后使用这些线程完成一个任务,等这些线程全部结束的时候,整个任务也算是结束了

void do_somthing3(unsigned int d) {
	cout << "task" << endl;
}

void func() {
	std::vector<std::thread> threads;
	for (unsigned i = 0; i < 20; ++i) {
	
		threads.push_back(std::thread(do_somthing3, i));
	}
	// mem_fn会根据成员函数指针推断可调用函数的类型,就省去了另外指定的步骤。
	std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));
		
}

int main() {

	func();
	return 0;
}

在这里我们可以看到,我们通过STL中的 vector装了20个线程,然后通过for_each遍历,并且调用join()

image.png