c++多线程(1)------创建和管理线程td::thread

  • 操作系统:ubuntu22.04
  • IDE:Visual Studio Code
  • 编程语言:C++11

算法描述

std::thread 是 C++11 标准库中用于创建和管理线程的核心类,定义在 头文件中。它使得多线程编程变得简单、类型安全且跨平台。

一、std::thread 简介

std::thread 是一个类,代表一个执行线程。你可以用它来启动一个函数(普通函数、lambda、函数对象、成员函数等)在新的线程中运行。
🔧 所需头文件:

#include <thread>

📦 常用成员函数:

函数说明
join()等待线程执行完毕(阻塞主线程)
detach()分离线程,让其在后台独立运行
get_id()获取线程的唯一 ID
joinable()判断线程是否可以 join 或 detach
swap()交换两个 thread 对象
native_handle()获取底层平台的原生线程句柄(如 pthread_t)

二、创建线程的多种方式(附示例)

  1. 使用普通函数
#include <iostream>
#include <thread>
#include <chrono>

void hello() 
{
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl;
}

int main()
{
    std::thread t(hello);  // 启动线程执行 hello 函数

    std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl;

    t.join();  // 等待线程结束

    std::cout << "Thread joined." << std::endl;
    return 0;
}

输出示例:

Main thread ID: 1
Hello from thread 2
Thread joined.
  1. 使用带参数的函数
void print_message(const std::string& msg, int n) 
{
    for (int i = 0; i < n; ++i) 
    {
        std::cout << msg << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
}

int main()
{
    std::thread t(print_message, "Hello from thread!", 3);

    std::cout << "Main thread is running..." << std::endl;

    t.join();

    std::cout << "Thread finished." << std::endl;
    return 0;
}
⚠️ 注意:参数是值传递的。如果要传引用,必须用 std::ref。

✅ 正确传递引用:

void increment(int& x)
{
    ++x;
}

int main() 
{
    int a = 0;
    std::thread t(increment, std::ref(a));  // 使用 std::ref 传引用
    t.join();
    std::cout << "a = " << a << std::endl;  // 输出: a = 1
    return 0;
}

✅ 3. 使用 Lambda 表达式

int main()
{
    int local = 10;

    std::thread t([local]()
    {  
        // 捕获 local 的副本
        std::cout << "Lambda: local = " << local << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    });

    t.join();
    return 0;
}
提示:如果要修改捕获的变量,使用 mutable 或传引用:
std::thread t([&local]() mutable 
{
    local += 5;
    std::cout << "Modified: " << local << std::endl;
});

✅ 4. 使用函数对象(仿函数)

struct Task 
{
    void operator()() const
    {
        std::cout << "Task executed in thread " << std::this_thread::get_id() << std::endl;
    }
};

int main() 
{
    std::thread t(Task{});
    t.join();
    return 0;
}

✅ 5. 使用类的成员函数

class Worker 
{
public:
    void work(int id)
     {
        std::cout << "Worker " << id << " working in thread " << std::this_thread::get_id() << std::endl;
    }
};

int main() 
{
    Worker w;
    std::thread t(&Worker::work, &w, 42);  // &w 是对象指针,42 是参数
    t.join();
    return 0;
}

🔍 解释:&Worker::work 是成员函数指针,&w 是对象地址,42 是参数。

三、join() vs detach()

❗ 每个 std::thread 对象在析构前必须被 join 或 detach,否则程序会 std::terminate()。
✅ join():等待线程结束

std::thread t(some_function);
t.join();  // 主线程阻塞,直到 t 结束
// 此时 t 不再可 join,t.joinable() 返回 false

✅ detach():分离线程

std::thread t(some_function);
t.detach();  // 线程在后台运行,不再与 thread 对象关联
// 不能再 join,线程生命周期由系统管理
⚠️ 分离线程的风险:如果主线程结束,整个程序终止,分离线程也会被强制终止。

🧪 四、检查线程状态:joinable()

std::thread t(some_function);

if (t.joinable()) 
{
    t.join();  // 安全调用
}
常用于异常安全代码中,确保线程被正确处理。

🧰 五、线程 ID 与当前线程操作

#include <iostream>
#include <thread>

void show_id() 
{
    std::cout << "This thread ID: " << std::this_thread::get_id() << std::endl;
}

int main() 
{
    std::thread t(show_id);
    std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl;

    t.join();

    // 让出 CPU 时间片
    std::this_thread::yield();

    // 休眠 500 毫秒
    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    return 0;
}

🧱 六、线程安全与共享数据(简单示例)

#include <iostream>
#include <thread>
#include <mutex>

int counter = 0;
std::mutex mtx;

void increment() 
{
    for (int i = 0; i < 100000; ++i) 
    {
        std::lock_guard<std::mutex> lock(mtx);
        ++counter;
    }
}

int main() 
{
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Final counter: " << counter << std::endl;  // 应为 200000
    return 0;
}
🔐 使用 std::mutex 和 std::lock_guard 保护共享变量 counter。

🚫 七、常见错误与注意事项

❌ 错误1:未调用 join 或 detach

int main()
{
    std::thread t([]{ std::cout << "Hello"; });
    return 0;  // t 析构时未 join/detach → 调用 std::terminate()
}

✅ 正确做法:

std::thread t(...);
// ...
t.join();  // 或 t.detach();

❌ 错误2:传递指针或引用时对象已销毁

std::thread t(increment, std::ref(local_var));
// 如果 local_var 是局部变量且线程未结束,可能访问已销毁内存
✅ 建议:确保线程使用的数据生命周期长于线程本身。

✅ 八、完整示例:多线程并行计算

#include <iostream>
#include <vector>
#include <thread>
#include <numeric>

void accumulate(const std::vector<int>& vec, int start, int end, int& result)
 {
    result = std::accumulate(vec.begin() + start, vec.begin() + end, 0);
}

int main() 
{
    std::vector<int> data(100000, 1);  // 10万个1
    int sum1 = 0, sum2 = 0;

    std::thread t1(accumulate, std::ref(data), 0, 50000, std::ref(sum1));
    std::thread t2(accumulate, std::ref(data), 50000, 100000, std::ref(sum2));

    t1.join();
    t2.join();

    std::cout << "Total sum: " << sum1 + sum2 << std::endl;  // 100000
    return 0;
}

总结:std::thread 核心要点

要点说明
✅ 启动线程std::thread t(func, args…)
✅ 等待结束t.join()
✅ 后台运行t.detach()
✅ 安全检查t.joinable()
✅ 线程IDt.get_id() 或 std::this_thread::get_id()
✅ 传引用使用 std::ref()
✅ 异常安全在异常路径中也要 join/detach

掌握 std::thread 是学习 C++ 多线程的第一步。结合 mutex、condition_variable 和 future,你就能构建出高效、安全的并发程序。建议多写小例子练习传参、生命周期管理、同步等关键点。

<think>好的,我现在要回答用户的问题:“列出C++中遵循happens-before的情况”。首先,我需要确认自己对happens-before关系的理解是否正确。Happens-before是C++内存模型中的一个核心概念,用于确定不同线程间操作的可见性顺序。用户可能希望了解在哪些情况下这种关系成立,以确保多线程程序的正确性。 接下来,我应该回忆C++标准中定义的happens-before的各种场景。首先想到的是单线程内的顺序,因为单线程中的操作自然有顺序,前面的操作happens-before后面的。然后是线程间的同步机制,比如互斥锁的加锁解锁操作,这可能涉及到mutex的释放获取。接着是原子操作的不同内存顺序,比如memory_order_releasememory_order_acquire之间的同步。还有像线程的启动结束,比如线程A中启动线程B,那么A中的启动操作happens-before线程B的所有操作。类似地,线程B的结束happens-before其他线程join它的操作。 此外,可能还有其他情况,比如使用条件变量时的通知等待操作,或者使用原子变量配合特定的内存顺序。比如,一个线程对原子变量进行store(release),另一个线程进行load(acquire),这样store之前的操作happens-before load之后的操作。还有内存屏障(fences)的使用,比如std::atomic_thread_fence,也可能建立happens-before关系。 用户可能需要具体的例子分类,所以需要将这些情况分门别类,并给出简明的解释。同时,需要注意术语的准确性,比如原子操作的不同内存顺序参数,以及它们如何影响happens-before关系。例如,memory_order_seq_cst比memory_order_relaxed提供更强的顺序保证。 可能需要检查是否遗漏了某些情况,比如线程间依赖关系,或者标准库中某些特定的函数调用(如std::async、std::promise等)是否隐式建立happens-before。例如,std::promise::set_value对应的std::future::get之间可能存在happens-before关系。 还要注意,某些情况下happens-before关系是通过组合多个机制实现的,比如同时使用互斥锁原子变量。这时候需要明确说明每种机制的作用。此外,应该指出哪些情况是显式的,哪些是隐式的,以帮助用户更好地理解应用。 可能遇到的误区是认为所有同步操作都会自动建立happens-before关系,但实际上需要符合特定的条件,比如正确的内存顺序参数或正确的同步原语使用。例如,普通的互斥锁(std::mutex)在lockunlock时自动包含内存屏障,而原子操作则需要显式指定内存顺序才能建立happens-before关系。 在组织回答时,应该采用结构化的方式,分点列出每种情况,并附上简短的代码示例或说明,以增强可读性。例如,使用标题子标题分类不同的情况,如单线程顺序、线程启动结束、互斥锁同步、原子操作、内存屏障等。同时,可能需要使用数学符号或公式来辅助说明,比如使用箭头表示happens-before的方向。 需要确保回答的准确性全面性,可能需要参考C++标准文档或权威资料,如cppreference.com的相关内容,以确认每种情况的正确性。例如,确认线程启动时的happens-before关系是否确实如所述,或者原子操作的不同内存顺序如何影响同步。 最后,总结各点,强调正确使用这些机制的重要性,以避免数据竞争未定义行为。提醒用户,即使某些情况下程序看似正确,但不符合happens-before规则,仍然可能导致不可预测的结果,尤其是在不同的硬件架构或编译器优化级别下。</think>### C++中 happens-before 关系的完整分类 (数学化表述) --- #### **1. 单线程顺序性** $$ \forall \text{操作} A, B \in \text{同一线程} \quad A \prec B \Rightarrow A \xrightarrow{\text{happens-before}} B $$ **示例**: ```cpp int x = 0; // 操作A x = 42; // 操作B (保证看到A的写入) ``` --- #### **2. 线程启动/终止** **线程启动**: $$ \text{线程创建操作} \xrightarrow{\text{hb}} \text{新线程的所有操作} $$ **线程终止**: $$ \text{线程所有操作} \xrightarrow{\text{hb}} \text{join()/detach()完成} $$ **代码验证**: ```cpp std::thread t; int x = 0; void worker() { assert(x == 42); // 必然成立 } int main() { x = 42; // 操作A t = std::thread(worker); // 操作B t.join(); assert(x == 42); // 必然成立 } ``` --- #### **3. 互斥锁同步** $$ \text{unlock()} \xrightarrow{\text{hb}} \text{后续的lock()} $$ **数学证明**: 设: - $L_1$: 线程1的lock - $U_1$: 线程1的unlock - $L_2$: 线程2的lock 则: $$ L_1 \prec U_1 \prec L_2 \Rightarrow U_1 \xrightarrow{\text{hb}} L_2 $$ --- #### **4. 原子操作同步** **释放-获取语义**: $$ \text{release写} \xrightarrow{\text{sync-with}} \text{acquire读} \Rightarrow \text{写前操作} \xrightarrow{\text{hb}} \text{读后操作} $$ **数学表示**: $$ W_{rel}(x) \xrightarrow{\text{sw}} R_{acq}(x) \Rightarrow \forall A \prec W_{rel}, \forall B \succ R_{acq}, A \xrightarrow{\text{hb}} B $$ **代码示例**: ```cpp std::atomic<bool> flag{false}; int data = 0; // 线程A data = 42; // 操作A flag.store(true, std::memory_order_release); // 操作B // 线程B while(!flag.load(std::memory_order_acquire)); // 操作C assert(data == 42); // 操作D (必然成立) ``` --- #### **5. 内存屏障强制排序** **栅栏同步**: $$ \text{release栅栏} \xrightarrow{\text{hb}} \text{acquire栅栏} $$ **数学建模**: $$ \text{Thread1: } A \prec \text{Fence}_{release} \prec B $$ $$ \text{Thread2: } C \prec \text{Fence}_{acquire} \prec D $$ $$ \Rightarrow A \xrightarrow{\text{hb}} D $$ --- #### **6. 依赖顺序 (Carries Dependency)** **指针依赖**: $$ B \text{依赖} A \Rightarrow A \xrightarrow{\text{hb}} B $$ **数学形式**: $$ \exists \text{指针} p, \quad p = f(A) \Rightarrow A \xrightarrow{\text{hb}} *p $$ **代码示例**: ```cpp int* p = nullptr; int data = 0; // 线程A data = 42; // 操作A std::atomic_store_explicit(&p, &data, std::memory_order_release); // 操作B // 线程B int* local_p = std::atomic_load_explicit(&p, std::memory_order_acquire); // 操作C if(local_p) assert(*local_p == 42); // 操作D (必然成立) ``` --- #### **7. 标准库工具** **Promise-Future 同步**: $$ \text{promise.set_value()} \xrightarrow{\text{hb}} \text{future.get()} $$ **数学证明**: $$ \exists ch \text{(通信信道)}, \quad \text{send}(ch) \xrightarrow{\text{hb}} \text{recv}(ch) $$ --- ### 综合关系图 ```mermaid graph TD A[单线程顺序] -->|程序顺序| B(操作可见性) C[线程启动] -->|创建顺序| B D[线程终止] -->|join顺序| B E[互斥锁] -->|unlock-lock| B F[原子操作] -->|release-acquire| B G[内存屏障] -->|fence配对| B H[依赖顺序] -->|指针/数据依赖| B I[Promise-Future] -->|值传递| B ``` --- ### 验证工具 **关系验证矩阵**: | 同步机制 | 是否跨线程 | 是否防编译器重排 | 是否防CPU重排 | |-------------------|------------|------------------|---------------| | 互斥锁 | ✅ | ✅ | ✅ | | 原子操作(seq_cst) | ✅ | ✅ | ✅ | | 原子操作(acq_rel) | ✅ | ✅ | ⚠️(部分架构) | | 内存屏障 | ✅ | ✅ | ✅ | | 线程生命周期 | ✅ | ✅ | ✅ | --- ### 关键结论 1. **组合使用原则**:$$ \text{正确同步} = \bigcup_{i=1}^n \text{有效happens-before机制} $$ 2. **性能代价**:越强的同步机制(如`memory_order_seq_cst`)性能损耗越高 3. **错误检测**:可通过ThreadSanitizer工具验证happens-before关系的正确性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

村北头的码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值