栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++ 多线程并发

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++ 多线程并发

目录
  • 2.1 线程管理基础
    • 2.1.1 启动线程 - 构造 std::thread 对象
      • 1.使用线程函数构造
      • 2.使用可调用类型构造
      • 3.使用 lambda 表达式构造
      • 4.明确 std::thread 的运行方式
    • 2.1.2 加入式 - 等待线程完成
    • 2.1.3 特殊情况下的等待
      • 1.避免应用被抛出的异常所终止 - 在异常处理过程中调用 join()
      • 2.资源获取即初始化方式(RAII,Resource Acquisition Is Initialization)
    • 2.1.4 分离式 - 后台运行线程

标准库头文件 thread
标准库线程类 std::thread

2.1 线程管理基础 2.1.1 启动线程 - 构造 std::thread 对象

使用可调用对象(函数、类、lambda)构造一个线程对象

1.使用线程函数构造
void do_some_work();
std::thread my_thread(do_some_work);
2.使用可调用类型构造
class background_task
{
public:
  void operator()() const
  {
    do_something();
    do_something_else();
  }
};
background_task f;
std::thread my_thread(f);

注意:如果可调用类型是临时变量,而不是一个命名的变量,C++编译器会将其解析为函数声明

std::thread my_thread(background_task());

这里相当与声明了一个名为 my_thread 的函数,这个函数带有一个参数(函数指针指向没有参数并返回 background_task 对象的函数),返回一个 std::thread 对象的函数,而非启动了一个线程
使用多组括号,或使用新统一的初始化语法,可以避免这个问题,如下所示:

std::thread my_thread((background_task()));  // 1
std::thread my_thread{background_task()};    // 2
3.使用 lambda 表达式构造
std::thread my_thread([]{
  do_something();
  do_something_else();
});
4.明确 std::thread 的运行方式

启动了线程,你需要明确是要等待线程结束(加入式),还是让其自主运行(分离式)
加入式和分离式分别需要使用 std::thread 的成员函数 join() 和 detach()

  • 注意1:必须在 std::thread 对象销毁之前做出决定,否则你的程序将会终止(std::thread 的析构函数会调用 std::terminate(),这时再去决定会触发相应异常)
  • 注意2:保证线程结束之前,可访问的数据的有效性

对象销毁之后再去访问,会产生未定义行为,如下所示:

struct func {
	int* i;
	func(int* i_) : i(i_) {}
	void operator() ()
	{
		for (unsigned j = 0; j < 1000000; ++j)
		{
			do_something(*i);
		}
	}
};

void oops_join()
{
	int* some_local_state = new int(0);
	func my_func(some_local_state);
	std::thread my_thread(my_func);
	delete some_local_state;
	my_thread.join();
	// 新线程试图访问已销毁的对象(some_local_state)
}

这种情况还可能发生在线程还没结束,函数已经退出的时候,这时线程函数还持有函数局部变量的指针或引用,下面展示了这样的一种情况:

void oops_detach()
{
	int some_local_state=0;
	func my_func(some_local_state);
	std::thread my_thread(my_func);
	my_thread.detach();
	// 不等待线程结束,新线程可能还在运行
}
2.1.2 加入式 - 等待线程完成

如果需要等待线程完成,需要使用 std::thread 的成员函数 join()
加入式可以确保局部变量在线程完成后,才被销毁
只能对一个线程使用一次 join(),一旦已经使用过 join(),std::thread 对象就不能再次加入了,当对其使用 joinable() 时,将返回 false

2.1.3 特殊情况下的等待 1.避免应用被抛出的异常所终止 - 在异常处理过程中调用 join()
void f() {
	auto my_func = []() {
		// do_something();
	};
	std::thread t(my_func);
	try
	{
		// do_something_in_current_thread();
	}
	catch (...)
	{
		t.join();
		throw;
	}
	t.join();
}
2.资源获取即初始化方式(RAII,Resource Acquisition Is Initialization)
class thread_guard {
    std::thread& t;
public:
    explicit thread_guard(std::thread& t_) : t(t_) {}
    ~thread_guard() {
        if (t.joinable())	// 判断线程是否已加入
            t.join();		// 如果没有,调用 join() 进行加入
    }
	// 直接对一个 std::thread 对象进行拷贝或赋值是危险的
    thread_guard(thread_guard const&) = delete;
    thread_guard& operator=(thread_guard const&) = delete;
};

void f() {
    auto my_func = []() {
        // do_something();
    };
    std::thread t(my_func);
    thread_guard g(t);
    // do_something_in_current_thread();
}

当线程执行完 do_something_in_current_thread() 后,局部对象就要被逆序销毁了
因此,thread_guard 对象 g 是第一个被销毁的,这时线程在析构函数中被加入到原始线程中

2.1.4 分离式 - 后台运行线程

如果需要后台运行线程,需要使用 std::thread 的成员函数 detach()
主线程不能与后台运行线程产生直接交互
注意:不能对没有/执行线程/的 std::thread 对象使用 detach()
当 std::thread 对象 t 使用 t.joinable(* 返回的是 true,才可以使用 t.detach()

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/971301.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号