C++11—thread库

thread库

概述

std::thread 是 C++11 标准库提供的线程管理类,它为我们提供了一种跨平台、面向对象的线程创建和管理方式。如果你之前接触过 Linux 下的 pthread 库或 Windows 下的线程 API,那么 std::thread 会让你感觉更加优雅和易用。

为什么需要 thread 库?

在 C++11 之前,创建线程需要依赖平台特定的 API:

  • Linux : 使用 pthread_create() 函数
  • Windows : 使用 CreateThread() API

这些 API 存在以下问题:

  1. 平台依赖性:不同平台需要不同的代码,难以实现跨平台
  2. 面向过程:使用函数指针,不够灵活
  3. 参数传递复杂:传递多个参数需要打包成结构体

std::thread 的出现解决了这些问题:

  • 跨平台:底层封装了各系统的线程库(Linux 的 pthread、Windows 的 Thread 等),提供统一的接口
  • 面向对象:采用类的方式管理线程,符合 C++ 的设计理念
  • 现代化特性:充分利用 C++11 的右值引用、移动语义、可变模板参数等特性,使用更加便捷

参考文档


构造函数

std::thread 提供了多种构造函数,让我们来看看它们的具体用法:

1. 默认构造函数

cpp 复制代码
thread() noexcept;

创建一个不表示任何线程的线程对象。这个对象是"空的",不能执行任何操作,主要用于后续的移动赋值。

cpp 复制代码
std::thread t;  // 创建一个空的线程对象
// t 此时不代表任何线程

2. 初始化构造函数(最常用)

cpp 复制代码
template <class Fn, class... Args>
explicit thread(Fn&& fn, Args&&... args);

这是日常开发中最常用的构造函数。它接受一个可调用对象(函数、函数指针、lambda 表达式、函数对象/仿函数等)和该可调用对象的参数。

最简单的调用示例

cpp 复制代码
#include <iostream>
#include <thread>

// 1. 普通函数
void func(int n)
{
    std::cout << "函数: " << n << std::endl;
}

// 2. 函数对象(仿函数)
struct Functor
{
    void operator()(int n)
    {
        std::cout << "仿函数: " << n << std::endl;
    }
};

int main()
{
    // 方式1:普通函数
    std::thread t1(func, 1);
    t1.join();
    
    // 方式2:函数指针
    void (*fp)(int) = func;
    std::thread t2(fp, 2);
    t2.join();
    
    // 方式3:lambda 表达式
    std::thread t3([](int n)
    {
        std::cout << "lambda: " << n << std::endl;
    }, 3);
    t3.join();
    
    // 方式4:函数对象(仿函数)
    Functor f;
    std::thread t4(f, 4);
    t4.join();
    
    // 方式5:临时函数对象
    std::thread t5(Functor(), 5);
    t5.join();
    
    return 0;
}

术语说明

  • 函数对象(Function Object) 也叫 仿函数(Functor) ,是指重载了 operator() 的类对象,可以像函数一样被调用

优势对比

相比 pthread_create 只能传递函数指针和单个 void* 参数:

c 复制代码
// pthread 方式:需要打包参数到结构体
struct ThreadArgs
{
    int n;
    int i;
};
void* Print(void* arg)
{
    ThreadArgs* args = (ThreadArgs*)arg;
    // ...
}
// 注意:需要确保 args 的生命周期足够长,或者使用动态分配
ThreadArgs args = {10, 0};
pthread_t tid;
pthread_create(&tid, NULL, Print, &args);
pthread_join(tid, NULL);  // 必须等待线程完成,否则 args 可能被销毁

std::thread 可以直接传递多个参数,类型安全且简洁:

cpp 复制代码
// std::thread 方式:直接传递参数
void Print(int n, int i)
{
    // ...
}
std::thread t(Print, 10, 0);  // 简洁明了!

3. 拷贝构造函数(已删除)

cpp 复制代码
thread(const thread&) = delete;
thread& operator=(const thread&) = delete;

重要std::thread 不支持拷贝构造和拷贝赋值。这是因为线程对象代表一个实际的执行线程,拷贝一个线程对象在语义上是不合理的。

cpp 复制代码
std::thread t1(Print, 10, 0);
// std::thread t2 = t1;  // ❌ 编译错误!不允许拷贝

4. 移动构造函数和移动赋值

cpp 复制代码
thread(thread&& x) noexcept;
thread& operator=(thread&& rhs) noexcept;

std::thread 支持移动语义,可以将一个线程对象的所有权转移给另一个线程对象。

cpp 复制代码
std::thread t1(Print, 10, 0);
std::thread t2 = std::move(t1);  // ✅ 移动构造,t1 变为空线程对象
// 现在 t2 拥有线程的所有权,t1 不再代表任何线程

使用场景:在容器中存储线程对象时,必须使用移动语义:

cpp 复制代码
std::vector<std::thread> threads;
threads.emplace_back(Print, 10, 0);  // 使用 emplace_back 直接构造
// 或者
std::thread t(Print, 10, 0);
threads.push_back(std::move(t));  // 使用移动语义

核心成员函数

join() - 等待线程完成

cpp 复制代码
void join();

join() 是线程同步的重要方法。它的作用是:阻塞当前线程(通常是主线程),直到被调用的线程执行完毕

为什么需要 join?

在多线程程序中,每个 std::thread 对象在销毁前必须调用 join()detach()。如果线程对象在销毁时仍然是一个可加入的线程(joinable),程序会调用 std::terminate() 终止。join() 确保了主线程会等待子线程完成后再继续执行,这是线程同步的标准做法。

什么是"可加入的线程"(joinable thread)?

一个线程对象是可加入的(joinable) ,意味着它代表一个活跃的执行线程 ,并且还没有被 join()detach()。具体来说:

可加入的线程(joinable = true)

  • ✅ 通过构造函数创建的线程对象,且线程正在运行或已完成但未 join
  • ✅ 可以通过 join() 等待线程完成
  • ✅ 可以通过 detach() 分离线程

不可加入的线程(joinable = false)

  • ❌ 默认构造的线程对象(空的线程对象)
  • ❌ 已经被 join() 的线程对象
  • ❌ 已经被 detach() 的线程对象
  • ❌ 已经被移动的线程对象(移动后变为空线程对象)

示例说明

cpp 复制代码
#include <iostream>
#include <thread>

void task()
{
    std::cout << "线程执行中..." << std::endl;
}

int main()
{
    // 情况1:可加入的线程
    std::thread t1(task);
    std::cout << "t1 joinable: " << t1.joinable() << std::endl;  // 输出: 1 (true)
    t1.join();
    std::cout << "join后 t1 joinable: " << t1.joinable() << std::endl;  // 输出: 0 (false)
    
    // 情况2:不可加入的线程(默认构造)
    std::thread t2;
    std::cout << "t2 joinable: " << t2.joinable() << std::endl;  // 输出: 0 (false)
    
    // 情况3:不可加入的线程(已分离)
    std::thread t3(task);
    t3.detach();
    std::cout << "detach后 t3 joinable: " << t3.joinable() << std::endl;  // 输出: 0 (false)
    
    // 情况4:不可加入的线程(已移动)
    std::thread t4(task);
    std::thread t5 = std::move(t4);
    std::cout << "移动后 t4 joinable: " << t4.joinable() << std::endl;  // 输出: 0 (false)
    std::cout << "t5 joinable: " << t5.joinable() << std::endl;  // 输出: 1 (true)
    t5.join();
    
    return 0;
}

重要规则

  1. 线程对象析构时的检查 :如果线程对象在析构时仍然是 joinable 的,程序会调用 std::terminate() 终止
  2. 必须 join 或 detach :每个 joinable 的线程对象在销毁前必须调用 join()detach()
  3. 只能 join 一次:一个线程对象只能 join 一次,再次 join 会抛出异常
  4. detach 后不能 join:detach 后的线程对象不能再 join
cpp 复制代码
#include <iostream>
#include <thread>

void task()
{
    std::cout << "子线程开始执行" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "子线程执行完毕" << std::endl;
}

int main()
{
    std::thread t(task);
    
    std::cout << "主线程等待子线程..." << std::endl;
    t.join();  // 主线程在这里阻塞,等待 t 执行完毕
    
    std::cout << "主线程继续执行" << std::endl;
    return 0;
}

注意事项

  • 一个线程对象只能调用一次 join()
  • 如果线程已经 join 过,再次调用会抛出 std::system_error 异常
  • 如果线程对象是空的(默认构造或已移动),调用 join() 也会抛出异常

detach() - 分离线程

cpp 复制代码
void detach();

join() 相反,detach() 将线程对象与底层线程分离,允许线程独立运行。分离后的线程成为"守护线程"(daemon thread),线程对象不再拥有该线程的所有权。

重要提示 :使用 detach() 时,需要确保线程的生命周期不会超出程序的生命周期。当 main() 函数返回时,程序会调用 std::exit(),这会终止所有仍在运行的分离线程。因此,detach() 通常用于不需要等待结果的长时间运行任务,且需要确保这些任务在程序退出前能够完成,或者使用适当的同步机制(如条件变量)来协调线程的退出。

cpp 复制代码
std::thread t(task);
t.detach();  // 分离线程,主线程不再等待它
// 注意:分离后不能再 join()

使用场景:适合不需要等待结果的长时间运行任务,如日志记录、后台监控等。

joinable() - 检查线程是否可 join

cpp 复制代码
bool joinable() const;

检查线程对象是否表示一个活跃的线程(可以调用 join()detach())。

cpp 复制代码
std::thread t(task);
if (t.joinable())
{
    t.join();  // 安全地 join
}

线程 ID

thread::id 类型

thread::idstd::thread 的嵌套类型(nested type),用于唯一标识一个线程。每个线程都有一个唯一的 ID。从底层角度看,thread::id 封装了各个平台的线程 ID 表示(Linux 的 pthread_t、Windows 的 DWORD 等),提供了统一的抽象接口。

特性

  • 支持比较操作(==, !=, <, <=, >, >=
  • 支持流插入和提取(<<, >>
  • 可以作为 std::unordered_mapstd::unordered_set 的键(通过特化的 hash 仿函数)

为什么需要封装?

不同平台的线程 ID 表示方式不同:

  • Linux: pthread_t 类型
  • Windows: DWORD 类型

thread::id 提供了一个统一的抽象,隐藏了平台差异,使得代码可以跨平台编译和运行。

get_id() - 获取线程 ID

cpp 复制代码
thread::id get_id() const;

通过线程对象获取线程 ID:

cpp 复制代码
std::thread t(task);
std::cout << "线程 ID: " << t.get_id() << std::endl;

this_thread::get_id() - 获取当前线程 ID

在线程执行体内,可以通过 std::this_thread::get_id() 获取当前线程的 ID:

cpp 复制代码
#include <iostream>
#include <thread>

void Print(int n, int i)
{
    for (; i < n; i++)
    {
        std::cout << std::this_thread::get_id() << ":" << i << std::endl;
    }
}

int main()
{
    std::thread t1(Print, 10, 0);
    std::thread t2(Print, 20, 10);
    
    // 获取线程 ID
    std::cout << "t1 的线程 ID: " << t1.get_id() << std::endl;
    std::cout << "t2 的线程 ID: " << t2.get_id() << std::endl;
    
    t1.join();
    t2.join();
    
    // 获取当前运行线程 ID(主线程)
    std::cout << "主线程 ID: " << std::this_thread::get_id() << std::endl;
    
    return 0;
}

完整示例

下面是一个综合示例,展示了 std::thread 的基本用法:

cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

std::mutex coutMutex;  // 用于保护 std::cout 的输出

void Print(int n, int i)
{
    // 锁放在循环外:保证一个线程完整输出所有内容,避免输出被其他线程打断
    std::lock_guard<std::mutex> lock(coutMutex);
    for (; i < n; i++)
    {
        std::cout << std::this_thread::get_id() << ":" << i << std::endl;
    }
    std::cout << std::endl;
}

int main()
{
    // 创建两个线程
    std::thread t1(Print, 10, 0);
    std::thread t2(Print, 20, 10);
    
    // 获取线程 ID
    std::cout << "t1 的线程 ID: " << t1.get_id() << std::endl;
    std::cout << "t2 的线程 ID: " << t2.get_id() << std::endl;
    
    // 等待线程完成
    t1.join();
    t2.join();
    
    // 获取当前运行线程 ID(主线程)
    std::cout << "主线程 ID: " << std::this_thread::get_id() << std::endl;
    
    return 0;
}

使用 Lambda 表达式

std::thread 同样支持 lambda 表达式,这让代码更加简洁:

cpp 复制代码
#include <iostream>
#include <thread>

int main()
{
    int n = 10;
    
    // 使用 lambda 表达式创建线程
    std::thread t([n]()
    {
        for (int i = 0; i < n; i++)
        {
            std::cout << std::this_thread::get_id() << ":" << i << std::endl;
        }
    });
    
    t.join();
    return 0;
}

使用函数对象(仿函数)

函数对象(也叫仿函数)是重载了 operator() 的类对象,可以像函数一样被调用:

cpp 复制代码
#include <iostream>
#include <thread>

// 函数对象(仿函数)
class Task
{
public:
    void operator()(int n)
    {
        for (int i = 0; i < n; i++)
        {
            std::cout << "函数对象执行: " << i << std::endl;
        }
    }
};

int main()
{
    Task taskObj;  // 创建函数对象
    
    // 直接传递函数对象
    std::thread t(taskObj, 10);
    
    t.join();
    return 0;
}

注意:也可以使用临时对象,但需要注意语法:

cpp 复制代码
// ✅ 正确:使用额外的括号避免被解析为函数声明
std::thread t((Task()), 10);

// 或者使用统一初始化语法
std::thread t{Task(), 10};

使用成员函数

如果需要在线程中调用类的成员函数,需要传递对象指针或引用:

cpp 复制代码
#include <iostream>
#include <thread>

class Worker
{
public:
    void doWork(int n)
    {
        for (int i = 0; i < n; i++)
        {
            std::cout << "工作线程: " << i << std::endl;
        }
    }
};

int main()
{
    Worker workerObj;
    
    // 传递对象指针和成员函数指针
    std::thread t(&Worker::doWork, &workerObj, 10);
    
    t.join();
    return 0;
}

传递引用参数

默认情况下,std::thread 会复制所有参数。如果需要传递引用,需要使用 std::refstd::cref

cpp 复制代码
#include <iostream>
#include <thread>
#include <functional>

void increment(int& n)
{
    n++;
    std::cout << "在子线程中: n = " << n << std::endl;
}

int main()
{
    int num = 42;
    std::cout << "主线程中: num = " << num << std::endl;  // 输出 42

    // ❌ 错误:会尝试复制引用类型,导致编译错误
    // 因为引用类型不能直接复制,必须使用 std::ref 包装
    // std::thread t(increment, num);  // 编译错误!

    // ✅ 正确:使用 std::ref 传递引用
    std::thread t(increment, std::ref(num));
    t.join();

    std::cout << "主线程中: num = " << num << std::endl;  // 输出 43
    return 0;
}

说明

  • std::ref() 用于传递非 const 引用
  • std::cref() 用于传递 const 引用
  • 如果不使用 std::ref,参数会被复制,修改不会影响原始变量

常见错误和注意事项

1. 忘记 join 或 detach

每个线程对象在销毁前必须调用 join()detach(),否则当线程对象析构时,如果线程仍然是可加入的(joinable),程序会调用 std::terminate() 终止。

cpp 复制代码
// ❌ 错误:线程对象销毁前没有 join 或 detach
{
    std::thread t(task);
    // 作用域结束,t 被销毁,但没有 join 或 detach
}  // 程序终止!

// ✅ 正确方式 1:显式 join
{
    std::thread t(task);
    t.join();
}

// ✅ 正确方式 2:使用 detach
{
    std::thread t(task);
    t.detach();
}

2. 多次调用 join

一个线程对象只能 join 一次:

cpp 复制代码
std::thread t(task);
t.join();
t.join();  // ❌ 抛出 std::system_error 异常

3. 参数的生命周期问题

确保传递给线程的参数在线程执行期间保持有效:

cpp 复制代码
// ❌ 危险:局部变量可能在线程执行前就被销毁
void badExample()
{
    int localVar = 42;
    std::thread t([&localVar]()
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << localVar << std::endl;  // 可能访问已销毁的变量
    });
    t.detach();  // 主函数返回,localVar 被销毁
}

// ✅ 正确:按值捕获或确保生命周期
void goodExample()
{
    int localVar = 42;
    std::thread t([localVar]()  // 按值捕获
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << localVar << std::endl;  // 安全
    });
    t.join();  // 等待线程完成
}

4. 异常安全

如果在线程创建后、join 前发生异常,可能导致线程对象没有正确 join:

cpp 复制代码
// ❌ 不够安全
void unsafe()
{
    std::thread t(task);
    someFunction();  // 如果这里抛出异常,t 可能没有 join
    t.join();
}

// ✅ 使用 RAII 确保异常安全
class ThreadGuard
{
    std::thread t;
public:
    explicit ThreadGuard(std::thread&& t_) : t(std::move(t_))
    {
    }
    ~ThreadGuard()
    {
        if (t.joinable())
        {
            t.join();
        }
    }
    ThreadGuard(const ThreadGuard&) = delete;
    ThreadGuard& operator=(const ThreadGuard&) = delete;
};

void safe()
{
    std::thread t(task);
    ThreadGuard guard(std::move(t));  // 移动线程所有权,确保即使发生异常也会 join
    someFunction();
}

与平台 API 的对比

为了更好地理解 std::thread 的优势,我们来看看它与平台特定 API 的对比:

Linux pthread

c 复制代码
#include <pthread.h>
#include <stdio.h>

void* task(void* arg)
{
    int* n = (int*)arg;
    printf("线程执行: %d\n", *n);
    return NULL;
}

int main()
{
    pthread_t tid;
    int n = 42;
    pthread_create(&tid, NULL, task, &n);
    pthread_join(tid, NULL);
    return 0;
}

Windows Thread API

cpp 复制代码
#include <windows.h>
#include <iostream>

DWORD WINAPI task(LPVOID lpParam)
{
    int* n = (int*)lpParam;
    std::cout << "线程执行: " << *n << std::endl;
    return 0;
}

int main()
{
    int n = 42;
    HANDLE hThread = CreateThread(
        NULL,           // 安全属性
        0,              // 栈大小
        task,           // 线程函数
        &n,             // 参数
        0,              // 创建标志
        NULL            // 线程 ID
    );
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    return 0;
}

C++11 std::thread

cpp 复制代码
#include <iostream>
#include <thread>

void task(int n)
{
    std::cout << "线程执行: " << n << std::endl;
}

int main()
{
    int n = 42;
    std::thread t(task, n);  // 简洁、类型安全、跨平台
    t.join();
    return 0;
}

对比总结

特性 pthread Windows API std::thread
跨平台
类型安全
参数传递 需要 void* 需要 void* 直接传递
代码简洁性 中等 复杂 简洁
面向对象

总结

std::thread 是 C++11 并发编程的基础,它为我们提供了:

  1. 跨平台的线程管理:一套代码,多平台运行
  2. 现代化的接口设计:充分利用 C++11 特性,使用更加便捷
  3. 类型安全:编译时类型检查,减少运行时错误
  4. 灵活的调用方式:支持函数、函数对象、lambda 表达式、成员函数等

掌握 std::thread 是学习 C++11 并发编程的第一步。在后续章节中,我们将学习如何配合互斥锁、条件变量等同步原语,构建更加复杂和强大的并发程序。


练习建议

  1. 编写一个程序,创建多个线程,每个线程打印不同的数字序列
  2. 尝试使用 lambda 表达式创建线程
  3. 实现一个简单的线程池,使用 std::vector<std::thread> 管理多个线程
  4. 思考:为什么 std::thread 不支持拷贝构造?移动语义在这里起到了什么作用?

综合示例:简单的线程池实现

下面是一个简单的线程池实现,展示了如何创建多个线程、使用 lambda 表达式,并确保异常安全:

cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <stdexcept>

// 简单的线程池类(异常安全)
class SimpleThreadPool
{
public:
    // 构造函数:创建指定数量的线程
    explicit SimpleThreadPool(size_t threadCount)
        : threadCount(threadCount)
    {
        threads.reserve(threadCount);
        
        // 使用 lambda 表达式创建多个线程,每个线程负责打印特定的数字
        for (size_t i = 0; i < threadCount; ++i)
        {
            threads.emplace_back([this, i, threadCount]()
            {
                // 每个线程负责打印特定模式的数字序列
                for (int num = static_cast<int>(i); num < 20; num += static_cast<int>(threadCount))
                {
                    // 等待轮到自己打印
                    // 注意:锁必须在循环内部,因为 cv.wait() 会释放锁并等待
                    // 如果锁在循环外面,一个线程会一直持有锁,其他线程无法执行
                    std::unique_lock<std::mutex> lock(mtx);
                    // cv.wait() 会自动释放锁,等待条件满足后重新获取锁
                    cv.wait(lock, [this, num]() { return currentNum == num; });
                    
                    // 打印数字(此时持有锁,保证输出和更新的原子性)
                    std::cout << "线程 " << i 
                              << " (ID: " << std::this_thread::get_id() 
                              << ") 打印: " << num << std::endl;
                    
                    // 更新当前数字,通知下一个线程(在持有锁的情况下更新)
                    currentNum++;
                    cv.notify_all();
                    // 锁在这里自动释放(lock 对象析构)
                }
            });
        }
    }

    // 析构函数:确保所有线程都被 join(异常安全)
    ~SimpleThreadPool()
    {
        // RAII:即使发生异常,也会确保所有线程被 join
        for (auto& t : threads)
        {
            if (t.joinable())
            {
                t.join();
            }
        }
    }

    // 禁止拷贝构造和拷贝赋值(因为 std::thread 不可拷贝)
    SimpleThreadPool(const SimpleThreadPool&) = delete;
    SimpleThreadPool& operator=(const SimpleThreadPool&) = delete;

    // 允许移动构造和移动赋值
    SimpleThreadPool(SimpleThreadPool&&) = default;
    SimpleThreadPool& operator=(SimpleThreadPool&&) = default;

    // 启动所有线程(开始打印)
    void start()
    {
        // 通知所有线程开始工作
        std::lock_guard<std::mutex> lock(mtx);
        cv.notify_all();
    }

    // 等待所有线程完成
    void waitAll()
    {
        for (auto& t : threads)
        {
            if (t.joinable())
            {
                t.join();
            }
        }
    }

private:
    std::vector<std::thread> threads;
    std::mutex mtx;                    // 互斥锁
    std::condition_variable cv;        // 条件变量
    int currentNum = 0;                // 当前应该打印的数字
    size_t threadCount;                // 线程数量
};

int main()
{
    // 创建包含4个线程的线程池
    SimpleThreadPool pool(4);
    
    // 启动所有线程(开始顺序打印)
    pool.start();
 
    // 等待所有线程完成
    pool.waitAll();
 
    std::cout << "所有线程执行完毕!数字已按顺序打印:0, 1, 2, 3, 4, 5..." << std::endl;
    
    return 0;
}

代码说明

  1. 多个线程顺序打印不同数字序列

    • 使用循环创建多个线程
    • 每个线程负责打印特定模式的数字(线程0: 0,4,8...,线程1: 1,5,9...)
    • 使用条件变量 std::condition_variable 控制打印顺序,确保数字按 0,1,2,3,4... 的顺序打印
    • 每个线程等待轮到自己打印的数字,打印后通知下一个线程
  2. 使用 lambda 表达式

    • 使用 emplace_back 直接构造线程,传入 lambda 表达式
    • Lambda 捕获 this、线程索引 i 和线程总数 threadCount(用于计算下一个要打印的数字)
  3. 简单的线程池实现

    • 使用 std::vector<std::thread> 管理多个线程
    • 提供 start() 方法启动所有线程
    • 提供 waitAll() 方法等待所有线程完成
  4. 异常安全(RAII)

    • 析构函数中确保所有线程都被 join()
    • 即使发生异常,析构函数也会被调用,保证资源正确释放
    • 禁止拷贝构造和拷贝赋值(因为 std::thread 不可拷贝)
    • 允许移动构造和移动赋值
  5. 同步机制

    • 使用 std::mutexstd::condition_variable 实现线程同步
    • 通过 currentNum 变量控制打印顺序
    • 每个线程使用 cv.wait() 等待轮到自己打印,打印后更新 currentNum 并通知其他线程
    • 锁的位置说明 :锁必须在 for 循环内部,原因如下:
      • cv.wait() 会自动释放锁并进入等待状态,被唤醒后重新获取锁
      • 如果锁在循环外面,一个线程会一直持有锁,其他线程无法执行,失去并发意义
      • 每次循环都需要等待条件变量,所以每次循环都需要创建新的锁对象
      • 锁保护的是"检查条件-打印-更新"这个原子操作,而不是整个循环

为什么 std::thread 不支持拷贝构造?

  • 线程对象代表一个实际的执行线程,拷贝一个线程对象在语义上不合理
  • 一个线程只能被一个线程对象管理,拷贝会导致所有权混乱
  • 移动语义允许转移线程的所有权,而不需要拷贝

移动语义的作用

  • 允许将线程对象的所有权从一个对象转移到另一个对象
  • 在容器中存储线程对象时,必须使用移动语义
  • 移动后,原线程对象变为空(不可 joinable),新对象拥有线程的所有权
相关推荐
txinyu的博客3 小时前
HTTP服务实现用户级窗口限流
开发语言·c++·分布式·网络协议·http
代码村新手3 小时前
C++-类和对象(上)
开发语言·c++
txinyu的博客4 小时前
map和unordered_map的性能对比
开发语言·数据结构·c++·算法·哈希算法·散列表
mjhcsp4 小时前
C++ 后缀数组(SA):原理、实现与应用全解析
java·开发语言·c++·后缀数组sa
hui函数4 小时前
如何解决 pip install 编译报错 ‘cl.exe’ not found(缺少 VS C++ 工具集)问题
开发语言·c++·pip
码农小韩4 小时前
基于Linux的C++学习——循环
linux·c语言·开发语言·c++·算法
消失的旧时光-19434 小时前
C++ 命名空间 namespace 讲透:从 std:: 到工程实践
开发语言·c++
linweidong5 小时前
C++ 中避免悬挂引用的企业策略有哪些?
java·jvm·c++
CoderCodingNo5 小时前
【GESP】C++五级/四级练习(双指针/数学) luogu-P1147 连续自然数和
开发语言·c++·算法