IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> (二)《C++ 并发编程》 | 线程的管理 -> 正文阅读

[C++知识库](二)《C++ 并发编程》 | 线程的管理


1. 基本线程管理

1.1 启动线程

通过给 std::thread 的构造函数传递一个可调用对象来启动线程。一旦启动了线程,需要显式决定是要等待它完成(结合),还是让它后台自行运行(分离)。如果在 std::thread 对象被销毁前未作决定,那么该线程将会被终止(std::thread 的构函数调用 std::terminate)。

如果不等待线程完成,那么需确保通过该线程访问的数据是有效的,直到该线程完成为止,否则将会出现未定义的行为。如,当线程函数持有局部变量的指针或引用,且当函数退出时线程尚未完成:

#include <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;
    // some_local_state 初始化结构体对象 my_func
    func my_func(some_local_state);
    // 基于 my_func 启动线程 my_thread
    std::thread my_thread(my_func);
    // 分离线程,让它在后台自行运行
    my_thread.detach();
}

首先,使用局部变量 some_local_state 初始化结构体对象 my_func,且成员变量 i 为该局部变量的引用。然后,使用可调用对象 my_func 启动线程 my_thread,并调用 detach 使新线程后台运行。

函数 oops 结束后,局部变量被销毁。而由于新线程 my_thread 处于后台运行,无法确定其运行状态。如果调用重载运算符 () 时循环还没有结束,此时成员变量 i 引用内容已被销毁,将导致未定义行为。

1.2 等待线程完成

将上述程序调用 detach 改为调用 join,将足以确保在函数退出前线程已结束。调用 join 将会清除所有与该线程相关联的存储器,它不再与任何线程相关联。只能对给定线程调用一次 join,此时调用 joinable 将返回 false。

如果要分离线程,通常在线程启动后就可立即调用 detach。如果需等待该线程结束,要确保 join 的正确调用。如,需同时在非异常情况和异常情况下调用 join:

void f()
{
    int some_local_state = 0;
    func my_func(some_local_state);
    std::thread t(my_func);
    try 
    {
        do_something_in_current_thread();
    }
    catch(...)
    {
    	// 异常情况下调用 join
        t.join();
        throw;
    }
    // 非异常情况下调用 join
    t.join();
}

或使用资源获取即初始化(RAII)的方式,提供一个类,在它的析构函数中调用 join:

class thread_guard
{
    std::thread& t;
public:
    explicit thread_guard(std::thread& t_) : t(t_) {}

    ~thread_guard()
    {
        if (t.joinable())
        {
            t.join();
        }
    }
	// 标记为 =delete 确保编译器不会自动生成
    thread_guard(thread_guard const&) = delete;
    thread_guard& operator=(thread_guard const&) = delete;  
};

void f()
{
    int some_locate_state = 0;
    func my_func(some_locate_state);
    std::thread t(my_func);

    // 在 thread_guard 的析构函数中结束线程,即使后续函数出现异常也无妨
    thread_guard g(t);  
	do_something_in_current_thread();
}

函数 f 结束后,局部对象会按照构造函数的逆序被销毁,g 首先被销毁并调用析构函数的 join 函数。即便后续函数出现异常,也能保证线程 t 的正确结束。


2. 传递参数给线程

传递参数给可调用对象或函数,就是将额外参数传递给 std::thread 的构造函数。如:

void f(int, char)
std::thread t(f, 3, 'c');

2.1 传递局部变量给线程

void f(int, std::string const&);
void oops(int param)
{
	char buffer[1024] = "Hello World!";
	std::thread t(f, 3, buffer);
	t.detach();
}

局部指针变量 buffer 传递给新线程 t 且函数的 f 参数类型为引用,同样会出现上述指针悬空的问题。此外,函数 oops 可能会在新线程中 buffer 被转换为 std::string 之前退出,同样产生未定义行为。后者的解决办法是显式转化:

std::thread t(f, 3, std::string(buffer));

2.2 传递引用给线程

对应于上述前者问题的解决办法:

std::thread t(f, 3, std::ref(std::string(buffer)));

2.3 传递 unique_ptr 给线程

在 unique_ptr 中,一个对象内的数据被转移至另一对象时,原对象变为空壳。移动构造函数和移动赋值运算符允许一个对象的所有权在 unique_ptr 实例之间进行转移。


3. 转移线程的所有权

一个特定执行线程的所有权可在 std::thread 实例之间移动。如:

void func1();
void func2();
std::thread t1(func1);
std::thread t2 = std::move(t1);
t1 = std::thread(func2);
std::thread t3;
t3 = std::move(t2);
t1 = std::move(t3);

首先,基于 func1 启动新线程 t1。然后当 t2 构建完成后,通过 std::move 显式地将 t1 的所有权转移至 t2。接着基于 func2 再次启动线程 t1,并声明一个不与任何线程关联的 t3。最后,使用两个 std::move 转移线程所有权。

在最后一次 std::move 中,由于 t1 已经与 func2 相关联,会调用 std::terminate 来终止程序,确保与 std::thread 的析构函数保持一致。


4. 在运行时选择线程数量

std::thread::hardware_currency 返回一个给定程序执行时能够真正并发运行的线程数量,并发运行时的线程数为该值与硬件线程数量的较小者。


5. 总结

  1. 以可调用对象为参数初始化 std::thread 对象即可启动线程。一旦线程启动,必须显式指定是要等待它结束(join),还是让它后台运行(detach)。
  2. 对于可调用对象的参数,将额外的参数传递给 std::thread 的构造函数即可,传递不同类型的参数将产生不同的行为。
  3. 线程的所有权可在 std::thread 之间转移。
  4. 并发程序运行时的线程数量受给定程序执行时能够运行的线程数和硬件线程数的影响。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-01 15:31:12  更:2022-05-01 15:31:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 5:06:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码