一、Thread库

        thread库中分有两个重量级嘉宾,一个是核心类thread;一个是命名空间this_thread

        std::thread 用于“管理别人”(创建和控制其他线程),而 std::this_thread 用于“了解自己”(操作当前正在执行的线程)

特性std::thread (类)std::this_thread (命名空间)
操作对象其他线程 (由该对象管理的线程)当前执行的线程 (调用函数所在的线程)
身份一个句柄,用于控制和管理一个特定的执行流。一个引用,指向当前正在运行的执行流,没有独立的句柄对象。
主要职责创建、等待 (join)、分离 (detach)、转移所有权 (move)、获取被管理线程的ID。获取当前线程ID (get_id)、让出CPU (yield)、休眠 (sleep_for, sleep_until)。
能否用于自身?不能。一个 std::thread 对象无法直接操作创建它的那个线程。例如,无法调用 t.join() 来等待自身。能。这是它的专长,专门用来查询和控制当前代码所在的线程。

thread类

thread - C++ Reference (cplusplus.com)

构造

#include <utility>
#include <functional>

namespace std {
    // 线程核心类:轻量级原生线程句柄封装
    class thread {
    public:
        // 线程ID类型:平台无关的唯一标识
        class id;
        // 原生线程句柄类型:平台相关(Linux:pthread_t/Windows:HANDLE)
        using native_handle_type = /* 平台原生线程句柄 */;

        // 1. 构造函数
        thread() noexcept;                // 默认构造:空线程,不创建OS线程
        template <class Fn, class... Args>
        explicit thread(Fn&& fn, Args&&... args); // 带任务构造:创建OS线程

        // 2. 禁用拷贝(线程不可拷贝,符合OS线程语义)
        thread(const thread&) = delete;
        thread& operator=(const thread&) = delete;

        // 3. 支持移动语义(线程所有权转移)
        thread(thread&& other) noexcept;
        thread& operator=(thread&& other) noexcept;

        // 4. 析构函数
        ~thread();

        // 5. 核心成员函数
        void join();                      // 等待线程执行完成(阻塞)
        void detach();                    // 分离线程:后台运行,自动释放资源
        bool joinable() const noexcept;   // 判断线程是否可等待/分离
        id get_id() const noexcept;       // 获取线程唯一ID
        native_handle_type native_handle();// 获取平台原生线程句柄
        static unsigned int hardware_concurrency() noexcept; // 获取CPU核心数

    private:
        // 核心私有成员:仅两个关键变量!
        native_handle_type _M_thread;     // 平台原生线程句柄(核心标识)
        id _M_id;                         // 线程唯一ID(平台无关)
    };

    // 线程ID类:轻量级标识,仅存储一个数值
    class thread::id {
    private:
        unsigned long long _M_thread_id;  // 线程ID数值(全局唯一)
        friend class thread;
    public:
        bool operator==(id) const noexcept;
        // 其他比较运算符、哈希支持...
    };
}
可变参数模板构造函数
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
  • 立即启动新线程

  • f 可以是:函数指针、lambda、函数对象、std::function

  • args 是传递给 f 的参数

void func(int a, double b, const std::string& s) {}

// 基本用法
std::thread t1(func, 42, 3.14, "hello");

// lambda
std::thread t2([](int x) { return x * x; }, 10);

// 成员函数
struct Foo {
    void bar(int x) {}
};
Foo foo;
std::thread t3(&Foo::bar, &foo, 42);  // 传入对象指针

参数传递的关键细节:

参数类型默认行为需要处理的情况
值类型拷贝传递直接传值
引用类型按值拷贝,无法传递引用!用 std::ref / std::cref
移动-only类型无法拷贝用 std::move
void modify(int& x) { x++; }      // 需要引用
void accept(std::unique_ptr<int> p) {}  // 移动-only

int n = 0;
std::unique_ptr<int> ptr(new int(42));

//  传递引用:必须用 std::ref
std::thread t1(modify, std::ref(n));

//  传递移动-only对象:必须用 std::move
std::thread t2(accept, std::move(ptr));

t1.join();
t2.join();
// n == 1, ptr 已被移空

        需要注意的是,在OS中每一个线程都具备唯一性(独立的PID、栈……)故无法说将一个线程拷贝,且很容易导致资源管理混乱,故thread禁止了拷贝构造。

        但是,thread类保留了移动构造,允许线程的管理权从一个对象交给另一个对象。保证了灵活性。

操作含义对 thread 意味着安全吗?
拷贝复制一份一模一样的两个对象管同一个线程 危险
移动把所有权移交过去只有新对象管线程,旧对象变空 安全
std::thread t1(func);
std::thread t2(std::move(t1));  // t2 接管线程
// t1 现在是空壳
t2.join();

成员函数

void join()
  • 阻塞当前线程,等待 *this 关联的线程执行完毕

  • 调用后 joinable() == false

  • 要求调用前 joinable() == true,否则抛出 std::system_error

std::thread t([]{ std::this_thread::sleep_for(std::chrono::seconds(1)); });
t.join();  // 主线程等待1秒
// t.joinable() == false
// t.join();  // 错误!重复调用会抛异常
void detach()
  • 将线程与 thread 对象分离,线程在后台独立运行

  • 调用后 joinable() == false

  • 线程结束后资源由运行时自动回收

  • 危险:主线程结束时进程终止,detach 的线程也会被强制终止

std::thread t([]{ 
    // 这个线程会在后台独立运行
    std::this_thread::sleep_for(std::chrono::seconds(10));
    std::cout << "This may never print if main ends early" << std::endl;
});
t.detach();
// 主线程继续执行,可能先于子线程结束
bool joinable() const noexcept;
  • 返回 true 的条件:线程正在运行(未被 join 也未被 detach)

std::thread::id get_id() const noexcept;
  • 返回线程的唯一标识符

  • 默认构造的对象返回 std::thread::id()

void swap(thread& other) noexcept;
  • 交换两个 thread 对象关联的线程所有权

std::thread t1(func1);
std::thread t2(func2);
t1.swap(t2);  // t1 现在关联 func2,t2 关联 func1
t1.join();
t2.join();

        其它的部分就不过多介绍,后面需要查表或询问ai即可。

总结表:

知识点底层本质
构造立即启动构造内部调用 pthread_create/CreateThread
join()调用 pthread_join,调用线程进入内核态等待
detach()调用 pthread_detach,线程结束自动回收资源
移动语义只复制/清空句柄,O(1),无系统调用
不可拷贝拷贝句柄会导致资源管理混乱
必须 join/detach析构函数检查 _M_joinable,若为 true 则 terminate
内存可见性构造含 release,join 含 acquire,保证内存同步

        核心法则:每个 joinable() == true 的 thread 对象,最终必须且只能调用一次 join() 或 detach()。

std::this_thread 命名空间

        std::this_thread 是一个命名空间而非类,它提供的所有函数都是无状态的,作用是让当前线程认识自己、控制自己。

// libstdc++ 简化实现
namespace this_thread {
    // 获取当前线程ID - 直接调用系统API
    inline thread::id get_id() noexcept {
        #ifdef __linux__
            return thread::id(pthread_self());  // POSIX
        #elif _WIN32
            return thread::id(GetCurrentThreadId());  // Windows
        #endif
    }
    
    // 让出CPU - 调用系统调度器
    inline void yield() noexcept {
        #ifdef __linux__
            sched_yield();  // POSIX
        #elif _WIN32
            SwitchToThread();  // Windows
        #endif
    }
    
    // 休眠 - 基于系统定时器
    template<typename Rep, typename Period>
    void sleep_for(const chrono::duration<Rep, Period>& rel_time) {
        #ifdef __linux__
            struct timespec ts = to_timespec(rel_time);
            nanosleep(&ts, nullptr);  // 或 clock_nanosleep
        #elif _WIN32
            Sleep(duration_cast<milliseconds>(rel_time).count());
        #endif
    }
    
    // 休眠直到绝对时间点
    template<typename Clock, typename Duration>
    void sleep_until(const chrono::time_point<Clock, Duration>& abs_time) {
        sleep_for(abs_time - Clock::now());
    }
}

        std::this_thread 的所有函数都是对操作系统系统调用的直接封装,不持有任何状态。

函数

方法功能用法示例核心注意事项(必读)
get_id()获取当前线程唯一标识auto id = std::this_thread::get_id();• 返回 std::thread::id,可作map key
• 默认构造的 thread::id() 表示"无线程",不等于主线程ID
• 线程结束后ID可能被系统回收重用
yield()主动让出CPU(仍可运行状态)std::this_thread::yield();• 唯一用途:优化自旋锁,避免CPU 100%
• 不保证其他线程一定会运行
• 不要用于延时/同步
sleep_for()休眠至少指定时长std::this_thread::sleep_for(100ms);• 实际休眠 ≥ 指定时间(系统精度通常10-15ms)
• 不用于精确计时
• sleep_for(0) 无效,请用 yield()
sleep_until()休眠到绝对时间点std::this_thread::sleep_until(deadline);• 周期性任务首选(自动补偿执行时间)
• 时间点已过则立即返回
• 比 sleep_for 更适合定时循环

三个最容易犯的错误

错误写法后果正确写法
if (id == std::thread::id()) 判断主线程永远为假先保存 static auto main_id = std::this_thread::get_id()
自旋锁:while(flag) {}CPU 100%while(flag) std::this_thread::yield();
sleep_for(1ms) 当作精确延时实际可能15ms接受误差,或改用实时系统

二、mutex(锁)

        锁的作用在之前的文章已经讲过,这里不复述。

std::mutex 核心方法

方法功能底层行为注意事项
lock()加锁,若已被锁则阻塞等待进入内核态,线程挂起可能抛出 std::system_error
unlock()解锁,唤醒等待线程唤醒等待队列中的一个线程只有锁的持有者才能调用
try_lock()尝试加锁,立即返回不阻塞,测试并设置返回 true 成功,false 已被锁
std::mutex mtx;

void safe_operation() {
    mtx.lock();
    // 临界区:访问共享数据
    shared_data++;
    mtx.unlock();  // 必须配对,否则死锁
}

        错误:lock() 后忘记 unlock(),或中途抛出异常导致 unlock() 未执行 → 死锁

RAII 锁管理

std::lock_guard - 最简单的 RAII 锁

template<typename Mutex>
class lock_guard {
private:
    Mutex& mtx;
public:
    explicit lock_guard(Mutex& m) : mtx(m) { mtx.lock(); }
    ~lock_guard() { mtx.unlock(); }
    // 禁止拷贝
    lock_guard(const lock_guard&) = delete;
};
void safe_operation() {
    std::lock_guard<std::mutex> lock(mtx);
    // 临界区:即使抛出异常,析构时也会自动解锁
    shared_data++;
}  // 自动解锁

std::unique_lock - 灵活的 RAII 锁

std::mutex mtx;

// 1. 延迟加锁
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// ... 做一些不需要锁的操作
lock.lock();  // 手动加锁

// 2. 提前解锁
lock.unlock();  // 临界区已结束,提前释放

// 3. 尝试加锁
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
    // 获得锁
} else {
    // 未获得锁
}

// 4. 超时尝试(需要 timed_mutex)
std::unique_lock<std::timed_mutex> lock(mtx, std::chrono::milliseconds(100));
if (lock.owns_lock()) {
    // 100ms 内获得了锁
}

// 5. 转移锁所有权
std::unique_lock<std::mutex> lock1(mtx);
std::unique_lock<std::mutex> lock2 = std::move(lock1);  // lock1 不再持有锁
特性lock_guardunique_lock
RAII 自动解锁✅✅
延迟加锁❌✅
提前解锁❌✅
超时尝试❌✅
移动语义❌✅
性能开销极小稍大(有状态

其他 mutex 类型

类型特点适用场景
std::mutex基础互斥锁,不支持递归默认选择
std::recursive_mutex同一线程可多次加锁递归函数、链式调用
std::timed_mutex支持超时 try_lock_for/until需要限时等待
std::recursive_timed_mutex递归 + 超时复杂场景
std::shared_mutex(C++17)共享锁(读)/独占锁(写)读多写少

总结:

类型/函数功能关键点
mtx.lock()加锁,阻塞必须配对 unlock(),否则死锁
mtx.unlock()解锁只有持有者能调用
mtx.try_lock()尝试加锁,不阻塞返回 bool
lock_guard<M>RAII 锁最常用,构造加锁析构解锁
unique_lock<M>灵活 RAII 锁支持延迟、提前、超时、移动
`std::lock(m1, m2...) | 同时锁定多个锁 | 避免死锁的原子操作 |
std::scoped_lock(C++17)多锁 RAII比 lock+guard 更简洁
std::adopt_lock标记锁已持有配合 std::lock 使用
std::defer_lock标记延迟加锁unique_lock 专用
std::try_to_lock标记尝试加锁unique_lock 专用

  mutex 保护共享数据,lock_guard 自动管理生命周期,std::lock 解决死锁问题。

call_once

  std::call_once 是一个函数模板,配合 std::once_flag 使用,保证多个线程同时调用时,指定的可调用对象只被执行一次。

底层原理:

  • once_flag 内部维护一个原子状态(未调用/调用中/已完成)

  • 第一个到达的线程执行函数,其他线程阻塞等待

    • 执行完成后,所有等待线程被唤醒,后续调用直接跳过

      // 简化实现示意
      class once_flag {
          atomic<int> state;  // 0=未调用, 1=调用中, 2=已完成
          mutex mtx;
          condition_variable cv;
      };
      
      template<typename Callable>
      void call_once(once_flag& flag, Callable&& func) {
          int expected = 0;
          if (flag.state.compare_exchange_strong(expected, 1)) {
              // 第一个线程:执行函数
              func();
              flag.state = 2;
              flag.cv.notify_all();  // 唤醒所有等待线程
          } else if (flag.state == 1) {
              // 其他线程:等待完成
              unique_lock lock(flag.mtx);
              flag.cv.wait(lock, [&]{ return flag.state == 2; });
          }
          // state == 2:直接返回
      }

      once_flag 的职责分解

      组件在 once_flag 中作用
      状态存储器atomic<int>记住"执行过了吗"
      竞争仲裁器CAS 操作的目标多个线程争抢"执行权"
      等待队列mutex + condition_variable(可选)让失败的线程阻塞等待
      生命周期锚点once_flag 对象本身多个 call_once 调用共用同一个状态

              once_flag 本身几乎什么都不干,它是 call_once 的工作台和数据区。且once_flag必须是独立的,否则每个 call_once 调用都不知道别人是否调用过

      基本用法:

      std::once_flag flag;
      
      void init_database(const std::string& host, int port) {
          std::cout << "连接数据库 " << host << ":" << port << std::endl;
      }
      
      void worker() {
          std::call_once(flag, init_database, "localhost", 3306);
      }

      异常处理

              核心特性:如果函数抛出异常,once_flag 会重置状态,允许后续再次调用。

      std::once_flag flag;
      int attempt = 0;
      
      void unstable_init() {
          attempt++;
          std::cout << "第 " << attempt << " 次尝试" << std::endl;
          if (attempt < 3) {
              throw std::runtime_error("初始化失败");
          }
          std::cout << "初始化成功" << std::endl;
      }
      
      void test() {
          try {
              std::call_once(flag, unstable_init);
          } catch (...) {
              // 异常后 flag 仍处于"未调用"状态
              // 下次 call_once 会再次执行
          }
      }
      
      int main() {
          test();  // 第1次:抛异常
          test();  // 第2次:抛异常
          test();  // 第3次:成功
          test();  // 第4次:直接跳过(已成功)
      }
      // 输出:
      // 第 1 次尝试
      // 第 2 次尝试
      // 第 3 次尝试
      // 初始化成功

更多推荐