C++线程管理

线程管理的基础

我们知道,每个程序至少有一个线程:执行main
()函数的线程,其余线程有各自的入口函数。线程与原始线程同时运行。

启动线程

线程会在std::thread对象创建的时候启动。其实使用C++线程库启动线程,可以归结为构造std::thread对象。

#include<thread>
using namespace std;

void do_some_work();
thread my_thread(do_some_work);

如同大多数C++标准库一样,std::thread可以用可调用类型构造,将带由于函数调用符类型的实例传入std::thread类中,替换默认的构造函数。

#include<thread>
using namespace std;

class background_task
{
   
public:
	void operator()() const
	{
   
		do_something();
		do_something_else();
	}
};

background_task f;
thread my_thread(f);

这段代码中,提供的函数对象会复制到新线程的存储空间当中。拷贝而成的函数对象的执行和调用都在新线程的内存空间中进行。函数对象的副本应与原始函数对象保持一致。

如果你传递了一个临时变量,而不是一个命名的变量;C++斌一起会将其解析为函数声明,而不是类型对象的定义。
std::thread my_thread(background_task());
这一句话相当于声明了一个名为my_thread的函数。

启动了线程,你需要明确的是要等待线程结束还是让其自主分离

如果不等待线程结束,就必须保重线程结束前,可访问的数据的有效性。

struct func
{
   
	int& i;
	
	func(int& i_): i(i_){
   }
	
	void operator()()
	{
   
		for(unsigned j = 0; j < 1000000; j++)
		{
   
			do_something(i);	//悬空引用
		}
	}
};

void oops()
{
   
	int some_local_state = 0;
	func my_func(some_local_state);

	thread my_thread(my_func);
	my_thread.detach();		//线程分离
}

当主线程结束的时候,如果子线程还在运行,它就会去调用do_something(i),函数,访问已经销毁的变量。

处理这种问题的常规方法:将数据复制到线程中,而非复制到共享数据中。

如果一个对象作为线程函数,这个对象就会复制到线程中,而后原始对象就会立即销毁。

等待线程完成

如果需要等待线程,就需要std::thread对象中的join()方法。同时,调用join()方法,还可以清理了线程相关的存储部分。这意味着,一个线程只能使用一次join()方法。

特殊情况下的等待

考虑一个情况,当线程运行之后产生异常,在join()调用之前抛出,就意味着这次调用会被跳过。所以需要在异常处理过程中调用join()

struct func;
void f()
{
   
	int some_local_state = 0;
	func my_func(some_local_state);
	thread t(my_func);
	
	try
	{
   
		do_something_in_current_thread();
	}
	catch(...)
	{
   
		t.join();
		throw;
	}
	t.join();
}

另外一种就是用面向对象的思想:封装成类

class thread_guard
{
   
	thread& t;
public: 
	explicit thread_guard(thread& t_);t(t_){
   }
	~thread_guard()
	{
   
		if(t.joinable())
		{
   
			t.join();
		}
	}
};

struct func;

void f()
{
   
	int some_local_state = 0;
	func my_func(some_local_state);
	thread t(my_func);
	thread_guard g(t);
	do_something_in_current_thread();
}

当然,如果不想等待线程结束,可以分离线程,从而避免异常安全问题。

后台运行线程

使用detach()会让线程在后台运行,这就意味着主线程不能与之直接交互。如果线程分离,那么就不可能有std::thread对象能引用它。通常也称分离线程为守护线程

守护线程就是没有任何显式的用户接口,并在后台运行的线程。这种线程的特点就是长时间运行。

thread t(do_background_work);
t.detach();
assert(!t.joinable());

下面就是一个让一个文字处理应用同时编辑多个文档的逻辑代码:

void edit_document(string const& filename)
{
   
	open_document_and_display_gui(filename);
	while(!done_editing())
	{
   
		user_command cmd = get_user_input();
		if(cmd.type == open_new_document)
		{
   
			string const new_name = get_filename_from_user();
			thread t(edit_document,new_name);	//不仅可传递调用寒暑表 ,还可传递参数
			t.detach();
		}
		else
		{
   
			process_user_input(cmd);
		}
	}
}

向线程函数传递参数

在传递参数的过程中,默认参数要拷贝到线程独立内存中,即使参数是引用形式,也可以在新线程中进行访问。

void f(int i,string const& s);
thread t(f, 3, "hello");

在这里传递的是一个字符串的字面值。之后,在线程的上下文中完成const char*string对象的转换。

当动态指针作为阐述传递给线程的时候,会出现崩溃

void f(int i, string const& s);
void oops(int some_param)
{
   
	char buffer[1024];
	sprintf(buffer,"%i",some_param);
	thread t(f, 3, buffer);
	t.detach();
}

这时函数很有可能在字面值转化成string对象之前崩溃(oops函数结束,栈帧回退)。

解决方案就是在传递到thread构造函数之前就将字面值转换成string对象。

但是!还有问题:
期望传递一个引用,但整个对象被复制了。当线程更新一个引用传递的数据结构是,这种情况就可能发生。

void update_date_for_widget(widget_id w, widget_date& date);
void opps_again(widget_id w)
{
   
	widget_data data;
	thread t(update_date_for_widget, w, date);
	display_status();
	t.join();
	process_widget_data(data);
}

在这段代码中,构造函数无视函数期待的参数类型,并盲目的拷贝已提供的变量。传递给函数的参数是data变量内部拷贝的引用,而非data本身的引用。

解决方案是std::ref,将参数转换为引用的形式:

thread t(up_date_data_for_widget, w, ref(data));

线程传递成员方法:

class X 
{
    
 public: 	
 	void do_lengthy_work(); 
 };

	X my_x; 
	thread t(&X::do_lengthy_work, &my_x); 

在这里,新线程将类方法作为线程函数。

有趣的是,虽然线程中传递的对象不能拷贝,但是可以用move()进行移动。

void process_big_object(std::unique_ptr<big_object>);

std::unique_ptr<big_object> p(new big_object);
p->prepare_data(42);
std::thread t(process_big_object,std::move(p));

转移线程所有权

刚刚我们提到了move(),这个其实也可以转移线程之间。

void some_function();
void some_other_function();

thread t1(some_function);
thread t2 = move(t1);	//t1的所有权转移给了t2,这时就由t2执行some_function
t1 = thread(some_other_function);	//因为是临时变量,所以move()移动 操作会隐式调用

thread 支持移动,就意味着线程的所有权可以在函数外进行转移。

thread f()
{
   
	void some_function();
	return thread(some_function);
}

thread g()
{
   
	void some_other_function(int);
	thread t(some_other_function, 42);
	return t;
}

当所有权可以在函数内部传递,就允许thread实例作为参数进行传递。

void f(thread t);

void g()
{
   
	void some_function();
	f(thread(some_function));
	thread t(some_function);
	f(move(t));
}

当然,也可以把线程对象放到容器里面:

void do_wrok(unsigned id);

void f()
{
   
	vector<thread> threads;
	for(unsigned i = 0; i < 20 ; ++i)
	{
   
		threads.push_back(thread(do_work, i));	//产生线程
	}
	
	for_each(threads.begin(),threads.end(),mem_fn(&thread::join()));		//对每个线程调用join()
}

运行时决定线程的数量

std::thread::hardware_concurrency()方法将返回能同时并发在一个程序中的线程数量。

在多核系统中,返回值可以使CPU的核芯数量。

在这里需要注意的是:启动线程数必须比num_threads少一个,因为已经存在了主线程了。

识别线程

线程主要通过线程id来进行识别:std::thread::id
可以通过调用std::thread::get_id()来获得线程id。如果std::thread对象没有和任何线程相关联,get_id()将返回std::thread::type默认构造值。

如果两个线程的id相等,那么它们要么是同一个线程,要么是没有线程。

std::thread::id master_thread();
void some_core_part_of_algorithm()
{
   
	if(std::this_thread::get_id() == master_thread)
	{
   
		do_master_thread_work();
	}
	do_common_work();
}

参考文献

[1]C++并发编程.Anthony Wilaiams
全部评论

相关推荐

10-16 22:56
门头沟学院 C++
1234567800:歌尔今年给211开14-15k吗,我本地人连面试都不给😂
点赞 评论 收藏
分享
小红书 后端开发 总包n+8w+期权
点赞 评论 收藏
分享
评论
点赞
收藏
分享
正在热议
# 25届秋招总结 #
442065次浏览 4508人参与
# 春招别灰心,我们一人来一句鼓励 #
41866次浏览 531人参与
# 阿里云管培生offer #
120195次浏览 2219人参与
# 地方国企笔面经互助 #
7957次浏览 18人参与
# 同bg的你秋招战况如何? #
76477次浏览 561人参与
# 虾皮求职进展汇总 #
115376次浏览 886人参与
# 北方华创开奖 #
107420次浏览 599人参与
# 实习,投递多份简历没人回复怎么办 #
2454553次浏览 34856人参与
# 实习必须要去大厂吗? #
55760次浏览 961人参与
# 提前批简历挂麻了怎么办 #
149886次浏览 1977人参与
# 投递实习岗位前的准备 #
1195903次浏览 18548人参与
# 你投递的公司有几家约面了? #
33203次浏览 188人参与
# 双非本科求职如何逆袭 #
662154次浏览 7394人参与
# 如果公司给你放一天假,你会怎么度过? #
4750次浏览 55人参与
# 机械人春招想让哪家公司来捞你? #
157622次浏览 2267人参与
# 如果你有一天可以担任公司的CEO,你会做哪三件事? #
11525次浏览 284人参与
# 发工资后,你做的第一件事是什么 #
12659次浏览 62人参与
# 工作中,努力重要还是选择重要? #
35779次浏览 384人参与
# 参加完秋招的机械人,还参加春招吗? #
20120次浏览 240人参与
# 我的上岸简历长这样 #
451995次浏览 8088人参与
# 实习想申请秋招offer,能不能argue薪资 #
39286次浏览 314人参与
# 非技术岗是怎么找实习的 #
155864次浏览 2120人参与
牛客网
牛客企业服务