目录
std::this_thread::sleep_until()
3. std::this_thread::sleep_for()
4. std::this_thread::sleep_until()
4.3、std::this_thread::sleep_for 与 std::this_thread::sleep_until 的区别
注:sleep_for 与 sleep_until 对比示例
4. 使用 std::scoped_lock 同时锁定多个互斥量
std::unique_lock 与 std::lock_guard 的区别
七、std::condition_variable::wait
std::condition_variable::wait 的原理
A、线程/多线程基础
一、C++11 创建线程的几种方式
1.1 使用函数指针
#include <iostream>
#include <thread>
void threadFunction(int x) {
    std::cout << "Thread function called with value: " << x << std::endl;
}
int main() {
    std::thread t(threadFunction, 10);
    t.join();  // 等待线程执行完毕
    return 0;
}
1.2 使用 lambda 表达式
#include <iostream>
#include <thread>
int main() {
    std::thread t([](int x) {
        std::cout << "Lambda thread called with value: " << x << std::endl;
    }, 20);
    t.join();  // 等待线程执行完毕
    return 0;
}
1.3 使用成员函数
#include <iostream>
#include <thread>
class MyClass {
public:
    void memberFunction(int x) {
        std::cout << "Member function called with value: " << x << std::endl;
    }
};
int main() {
    MyClass obj;
    std::thread t(&MyClass::memberFunction, &obj, 30);
    t.join();  // 等待线程执行完毕
    return 0;
}
1.4 使用可调用对象 (Functor)
#include <iostream>
#include <thread>
class Functor {
public:
    void operator()(int x) {
        std::cout << "Functor called with value: " << x << std::endl;
    }
};
int main() {
    Functor functor;
    std::thread t(functor, 40);
    t.join();  // 等待线程执行完毕
    return 0;
}
二、定义一个线程类
#include <iostream>
#include <thread>
class ThreadClass {
private:
    std::thread t;
public:
    // 构造函数,接受函数和参数来启动线程
    template <typename Callable, typename... Args>
    explicit ThreadClass(Callable&& func, Args&&... args) {
        t = std::thread(std::forward<Callable>(func), std::forward<Args>(args)...);
    }
    // 加入线程(等待线程完成)
    void join() {
        if (t.joinable()) {
            t.join();
        }
    }
    // 分离线程(让线程独立运行)
    void detach() {
        if (t.joinable()) {
            t.detach();
        }
    }
    // 析构函数,确保线程对象在销毁前已被处理
    ~ThreadClass() {
        if (t.joinable()) {
            t.join();  // 通常在析构时,选择 join 或 detach 来处理未处理的线程
        }
    }
};
void threadFunction(int x) {
    std::cout << "Thread function running with value: " << x << std::endl;
}
int main() {
    // 使用类来管理线程
    ThreadClass threadObj(threadFunction, 50);
    threadObj.join();  // 等待线程完成
    return 0;
}
三、join() 与 detach() 的详细用法及区别
 
3.1 join() 的用法
 
 
#include <iostream>
#include <thread>
void task() {
    std::cout << "Task is running in thread." << std::endl;
}
int main() {
    std::thread t(task);
    // 主线程等待子线程完成
    std::cout << "Waiting for thread to finish..." << std::endl;
    t.join();  // 阻塞,直到子线程完成
    std::cout << "Thread has finished." << std::endl;
    return 0;
}
解释:
3.2 detach() 的用法
 
 
示例:
#include <iostream>
#include <thread>
#include <chrono>
void task() {
    std::cout << "Detached thread is running." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 模拟耗时操作
    std::cout << "Detached thread finished." << std::endl;
}
int main() {
    std::thread t(task);
    // 分离线程,允许其在后台运行
    t.detach();
    // 主线程不等待子线程完成,继续执行
    std::cout << "Main thread is not waiting for detached thread." << std::endl;
    // 模拟主线程的其他操作
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Main thread finished." << std::endl;
    return 0;
}
解释:
3.3 join() 与 detach() 的区别总结
 
 
四、std::this_thread
 
 
4.1、主要功能
4.2、各方法的用法及适用场景
总结
std::this_thread 提供了一个简单而强大的接口,用于控制当前线程的行为,包括:
- 获取当前线程的 ID:有助于在调试和日志记录中识别不同线程。
- 让出线程的执行权:通过 yield()提高 CPU 使用效率,允许系统调度其他线程。
- 让线程休眠:可以使用 sleep_for或sleep_until暂时暂停线程的执行,应用场景包括定时任务、延迟执行等。
这些工具使得 C++11 开始的多线程编程变得更加灵活和易于控制。
五、std::mutex 详解
 
std::mutex 是 C++11 引入的一种线程同步机制,用于在多线程环境下防止数据竞争(race condition)。在多线程程序中,如果多个线程同时访问并修改同一个共享数据,会导致数据竞争问题,造成不可预知的错误。std::mutex(互斥量)提供了一种机制,确保每次只有一个线程能够访问共享资源,其他线程必须等待,直到前一个线程释放互斥量。
主要功能
- lock():锁定互斥量。如果互斥量已经被其他线程锁定,则调用- lock()的线程将会阻塞,直到该互斥量被解锁。
- unlock():解锁互斥量,允许其他线程锁定它。
- try_lock():尝试锁定互斥量。如果互斥量已被其他线程锁定,则返回- false,否则锁定并返回- true。
使用场景
代码示例
1. 基本使用 std::mutex
 
这是一个简单的例子,展示了如何使用 std::mutex 来保护共享数据(计数器),防止多个线程同时修改它。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;  // 全局互斥量,用于同步对共享数据的访问
int counter = 0; // 共享资源
void increaseCounter() {
    for (int i = 0; i < 10000; ++i) {
        mtx.lock();   // 锁定互斥量,进入临界区
        ++counter;    // 对共享数据进行操作
        mtx.unlock(); // 解锁互斥量,离开临界区
    }
}
int main() {
    // 创建两个线程,执行 increaseCounter 函数
    std::thread t1(increaseCounter);
    std::thread t2(increaseCounter);
    // 等待两个线程完成
    t1.join();
    t2.join();
    // 输出最终的计数器值
    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}
解释:
- 互斥量 mtx:在每个线程访问共享资源之前使用lock()来锁定互斥量,保证在任何时候只有一个线程可以修改counter变量。
- 临界区:临界区是指 mtx.lock()和mtx.unlock()之间的代码区域,即对共享资源的访问部分。
- 线程安全:在互斥量的保护下,两个线程不会同时修改 counter,因此保证了线程安全。
输出示例:
Final counter value: 20000
如果没有使用 std::mutex,两个线程可能同时访问并修改 counter,导致最后输出的值小于预期的 20000,因为多个线程可能在同一时刻操作同一内存位置,发生了数据竞争。
2. 使用 try_lock()
 
try_lock() 是 std::mutex 提供的另一个方法,它不会阻塞线程。如果互斥量已经被锁定,try_lock() 会立即返回 false,而不是阻塞等待。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;  // 全局互斥量
void printMessage(const std::string& msg) {
    if (mtx.try_lock()) {  // 尝试获取锁,如果成功则执行
        std::cout << msg << std::endl;
        mtx.unlock();      // 解锁
    } else {
        std::cout << "Lock busy, couldn't print: " << msg << std::endl;
    }
}
int main() {
    std::thread t1(printMessage, "Hello from Thread 1");
    std::thread t2(printMessage, "Hello from Thread 2");
    t1.join();
    t2.join();
    return 0;
}
解释:
- try_lock()方法尝试锁定互斥量。如果成功获取锁,它就进入临界区执行输出操作;如果未能获取锁,说明其他线程已锁定了互斥量,则输出 "Lock busy"。
输出示例:
Hello from Thread 1
Lock busy, couldn't print: Hello from Thread 2
3. 使用 std::lock_guard 简化互斥锁管理
 
C++11 提供了 std::lock_guard,它是一个 RAII 风格的锁管理器,在作用域结束时自动释放锁,避免了手动 lock() 和 unlock() 可能引发的错误。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;  // 全局互斥量
int counter = 0; // 共享资源
void increaseCounter() {
    for (int i = 0; i < 10000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);  // 自动锁定互斥量,作用域结束时自动解锁
        ++counter;
    }
}
int main() {
    std::thread t1(increaseCounter);
    std::thread t2(increaseCounter);
    t1.join();
    t2.join();
    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}
解释:
- std::lock_guard是一个模板类,管理互斥锁的生命周期。它在构造时自动锁定互斥量,在作用域结束时(例如函数返回或异常抛出时)自动解锁,避免了忘记解锁或出现死锁的情况。
输出示例:
Final counter value: 20000
4. 使用 std::scoped_lock 同时锁定多个互斥量
 
C++17 引入了 std::scoped_lock,它不仅可以同时锁定多个互斥量,还能防止死锁。std::scoped_lock 是 std::lock_guard 的改进版,特别适用于需要同时锁定多个资源的场景。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1, mtx2;  // 两个互斥量
void task1() {
    std::scoped_lock lock(mtx1, mtx2);  // 同时锁定 mtx1 和 mtx2
    std::cout << "Task 1 has locked both mutexes." << std::endl;
}
void task2() {
    std::scoped_lock lock(mtx1, mtx2);  // 同时锁定 mtx1 和 mtx2
    std::cout << "Task 2 has locked both mutexes." << std::endl;
}
int main() {
    std::thread t1(task1);
    std::thread t2(task2);
    t1.join();
    t2.join();
    return 0;
}
解释:
- std::scoped_lock同时锁定多个互斥量,并且能避免死锁。它会确保在解锁时,以相反顺序解锁,从而避免了交叉锁定不同互斥量时可能发生的死锁问题。
输出示例:
Task 1 has locked both mutexes.
Task 2 has locked both mutexes.
重要注意事项
- 锁的生命周期:互斥量应该在所有访问共享资源的线程中共享。一个线程加锁后,其他线程必须等待该锁被解锁才能继续执行。
- 防止死锁:在多个互斥量之间锁定时(如同时访问多个资源),应小心防止死锁。死锁发生在多个线程相互等待其他线程释放锁的情况下。使用 std::scoped_lock可以简化锁定多个资源时的死锁管理。
- lock_guard和- scoped_lock的推荐使用:- lock_guard和- scoped_lock是 RAII 风格的锁管理工具,推荐使用这些工具来管理锁的生命周期,避免手动- lock()和- unlock()可能引发的错误。
总结
std::mutex 是 C++11 提供的基本线程同步工具,用于解决多线程编程中的数据竞争问题。通过互斥量可以确保同一时刻只有一个线程能够访问共享资源,防止多个线程同时修改数据而导致不可预测的行为。结合 std::lock_guard 和 std::scoped_lock,可以更简便地管理锁的生命周期,确保代码更加安全和高效。
六、std::unique_lock
 
 
- 可以延迟锁定互斥量(懒惰锁定)。
- 可以在需要时手动解锁和重新锁定。
- 可以使用 std::try_lock、std::defer_lock和std::adopt_lock等高级锁定策略。
主要功能:
使用示例
1. 基本使用:自动锁定与解锁
std::unique_lock 可以像 std::lock_guard 一样,在构造时自动锁定互斥量,并在析构时自动解锁。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;  // 全局互斥量
int counter = 0; // 共享资源
void increaseCounter() {
    for (int i = 0; i < 10000; ++i) {
        std::unique_lock<std::mutex> lock(mtx);  // 自动锁定互斥量
        ++counter;  // 临界区
        // lock 会在作用域结束时自动解锁
    }
}
int main() {
    std::thread t1(increaseCounter);
    std::thread t2(increaseCounter);
    t1.join();
    t2.join();
    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}
解释:
- std::unique_lock的构造函数会立即锁定互斥量- mtx,并且当- lock作用域结束时,它会自动解锁。这种机制避免了手动调用- unlock()的必要。
2. 手动锁定与解锁
你可以通过 std::unique_lock 对互斥量进行手动锁定和解锁操作,控制更为灵活。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void task() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);  // 延迟锁定互斥量
    std::cout << "Before locking" << std::endl;
    
    lock.lock();  // 手动锁定互斥量
    std::cout << "Locked and working..." << std::endl;
    
    lock.unlock();  // 手动解锁
    std::cout << "Unlocked" << std::endl;
}
int main() {
    std::thread t(task);
    t.join();
    return 0;
}
解释:
- 这里使用了 std::defer_lock,std::unique_lock在构造时不会自动锁定互斥量。随后,我们手动调用了lock()和unlock()来控制互斥量的锁定和解锁过程。
3. 使用 try_lock 尝试锁定
 
std::unique_lock 还可以用于尝试锁定互斥量,而不阻塞线程。如果锁定失败,线程可以继续执行其他任务。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void task() {
    std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);  // 尝试锁定互斥量
    if (lock.owns_lock()) {
        std::cout << "Lock acquired, working..." << std::endl;
    } else {
        std::cout << "Couldn't acquire lock, doing other work..." << std::endl;
    }
}
int main() {
    std::thread t1(task);
    std::thread t2(task);
    t1.join();
    t2.join();
    return 0;
}
解释:
- 通过 std::try_to_lock,线程会尝试锁定互斥量。如果无法锁定,程序可以继续执行其他任务,而不是阻塞等待锁的释放。
- owns_lock()可以检查- std::unique_lock是否成功锁定互斥量。
4. 使用 adopt_lock 接管已经锁定的互斥量
 
如果你手动锁定了互斥量,但希望将它交给 std::unique_lock 来管理,可以使用 std::adopt_lock。这会告诉 std::unique_lock,互斥量已经被锁定,不再需要再次锁定。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void task() {
    mtx.lock();  // 手动锁定互斥量
    std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);  // 接管锁
    std::cout << "Lock adopted, working..." << std::endl;
    // lock 会在作用域结束时自动解锁
}
int main() {
    std::thread t(task);
    t.join();
    return 0;
}
std::unique_lock 与 std::lock_guard 的区别
 
-  灵活性: - std::lock_guard在构造时立即锁定互斥量,并在销毁时自动解锁。它适合用于简单的锁管理。
- std::unique_lock提供了更多灵活的功能,如延迟锁定、尝试锁定、手动解锁等。它适用于需要复杂锁定逻辑的场景。
 
-  锁的控制: - std::lock_guard不允许手动解锁,只能在作用域结束时解锁。
- std::unique_lock允许在代码中手动调用- unlock(),并且可以手动重新锁定。
 
-  性能: - 由于 std::lock_guard更简单,通常它的性能要比std::unique_lock稍高一些。但如果你的程序不需要std::unique_lock的灵活性,优先使用std::lock_guard会更高效。
 
- 由于 
总结
std::unique_lock 是一个灵活的锁管理工具,适合处理复杂的线程同步场景。它与 std::lock_guard 的区别在于其灵活的控制能力:支持延迟锁定、手动解锁、尝试锁定等操作。此外,std::unique_lock 可以使用 std::defer_lock、std::try_to_lock 和 std::adopt_lock 等锁定策略,适用于需要更精细控制的场景。在简单的锁定场景中,std::lock_guard 更适合,而 std::unique_lock 则适用于需要更多灵活性的场景。
七、std::condition_variable::wait
 
是 C++11 中引入的一个机制,用于在线程同步时协调多个线程之间的执行。它与 std::mutex 和 std::unique_lock 配合使用,用于阻塞线程直到特定条件满足。
std::condition_variable::wait 的原理
 
- 互斥锁:wait()必须与一个互斥锁(std::unique_lock)一起使用。它在调用wait()时会自动释放锁,进入等待状态,并在被通知时重新获取锁。
- 条件变量:wait()用于阻塞线程,直到另一个线程通过条件变量调用notify_one()或notify_all()来通知等待线程可以继续执行。
- 通知机制: 
  - notify_one():唤醒一个等待的线程。
- notify_all():唤醒所有等待的线程。
 
wait() 的使用场景
 
典型的 wait() 使用场景是生产者-消费者问题。在这种情况下,消费者等待生产者生成数据,而生产者在生成数据后通知消费者继续处理。
代码示例
1. 基本使用 wait() 和 notify_one()
 
这是一个简单的示例,其中一个线程等待某个条件满足,而另一个线程在一定时间后设置条件并通知等待的线程继续执行。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;                    // 互斥锁
std::condition_variable cv;        // 条件变量
bool ready = false;                // 条件标志
void waitForWork() {
    std::unique_lock<std::mutex> lock(mtx);  // 获取锁
    std::cout << "Waiting for work to be ready...\n";
    
    // 当 ready 为 false 时,线程进入等待状态,锁被自动释放
    cv.wait(lock, [] { return ready; });
    // 当 ready 为 true 时,wait 结束,线程重新获取锁
    std::cout << "Work is ready, proceeding...\n";
}
void prepareWork() {
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 模拟工作准备
    {
        std::lock_guard<std::mutex> lock(mtx);  // 获取锁并设置条件
        ready = true;
        std::cout << "Work is prepared, notifying worker...\n";
    }
    cv.notify_one();  // 通知等待线程可以继续执行
}
int main() {
    std::thread worker(waitForWork);   // 创建等待线程
    std::thread preparer(prepareWork); // 创建准备线程
    worker.join();
    preparer.join();
    return 0;
}
解释:
- wait():在- waitForWork()函数中,调用- cv.wait(lock, [] { return ready; }),它将当前线程阻塞,直到- ready变为- true。- wait()在等待期间自动释放锁,避免其他线程无法获取锁。
- notify_one():在- prepareWork()函数中,调用- cv.notify_one()通知等待的线程(- waitForWork)可以继续执行。
- 锁的管理:wait()和notify_one()都需要与互斥锁配合使用,以保证线程之间的同步。
输出示例:
Waiting for work to be ready...
Work is prepared, notifying worker...
Work is ready, proceeding...
2. 使用 notify_all() 唤醒所有等待线程
 
notify_all() 可以唤醒所有因条件不满足而阻塞的线程。这对于多个线程等待同一个条件的场景非常有用。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] { return ready; });  // 所有线程都等待 ready 为 true
    std::cout << "Worker " << id << " is proceeding.\n";
}
void prepareWork() {
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 模拟工作准备
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
        std::cout << "Work is ready, notifying all workers...\n";
    }
    cv.notify_all();  // 通知所有等待的线程
}
int main() {
    std::vector<std::thread> workers;
    
    // 创建多个线程
    for (int i = 1; i <= 5; ++i) {
        workers.emplace_back(worker, i);
    }
    std::thread preparer(prepareWork);  // 创建准备线程
    // 等待所有线程完成
    for (auto& t : workers) {
        t.join();
    }
    preparer.join();
    return 0;
}
解释:
- notify_all():- notify_all()会唤醒所有等待的线程,这里唤醒了 5 个- worker线程。
- 多线程等待:所有 worker线程都会在cv.wait()中等待,直到ready变为true并且收到通知。
输出示例:
Work is ready, notifying all workers...
Worker 1 is proceeding.
Worker 2 is proceeding.
Worker 3 is proceeding.
Worker 4 is proceeding.
Worker 5 is proceeding.
3. 防止虚假唤醒
虚假唤醒(spurious wakeups)是指线程在没有被条件变量显式唤醒的情况下,也可能会被唤醒。这是操作系统的特性,因此我们在使用 wait() 时应该总是配合条件判断来避免这种情况。
为了避免虚假唤醒,C++ 中的 wait() 提供了一种安全的模式,即使用条件判断的 lambda 表达式来确保条件满足时才继续执行。
cv.wait(lock, [] { return ready; });
这个版本的 wait() 会在 ready 为 false 时继续阻塞线程,直到条件真正满足。
4. wait_for() 和 wait_until()
 
除了 wait() 之外,C++11 还提供了 wait_for() 和 wait_until() 方法,允许我们指定等待的时间。如果在指定的时间内条件未满足,线程会超时并继续执行。
- wait_for():阻塞线程一段时间,超时后线程自动返回。
- wait_until():阻塞线程直到某个时间点,超时后线程自动返回。
4.1 使用 wait_for() 超时等待
 
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
    std::unique_lock<std::mutex> lock(mtx);
    if (cv.wait_for(lock, std::chrono::seconds(3), [] { return ready; })) {
        std::cout << "Work is ready, proceeding...\n";
    } else {
        std::cout << "Timeout, work is not ready.\n";
    }
}
void prepareWork() {
    std::this_thread::sleep_for(std::chrono::seconds(5));  // 模拟准备时间
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
        std::cout << "Work is prepared, notifying worker...\n";
    }
    cv.notify_one();
}
int main() {
    std::thread t1(worker);
    std::thread t2(prepareWork);
    t1.join();
    t2.join();
    return 0;
}
解释:
- wait_for():等待 3 秒,如果- ready在 3 秒内没有变为- true,线程会超时并继续执行。
- 超时处理:如果超过等待时间,线程会输出 "Timeout, work is not ready."。
输出示例:
Timeout, work is not ready.
Work is prepared, notifying worker...
4.2 使用 wait_until() 等待到指定时间点
 
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
    auto timeout = std::chrono::system_clock::now() + std::chrono::seconds(3);
    std::unique_lock<std::mutex> lock(mtx);
    if (cv.wait_until(lock, timeout, [] { return ready; })) {
        std::cout << "Work is ready, proceeding...\n";
    } else {
        std::cout << "Timeout, work is not ready.\n";
    }
}
void prepareWork() {
    std::this_thread::sleep_for(std::chrono::seconds(5));  // 模拟准备时间
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
        std::cout << "Work is prepared, notifying worker...\n";
    }
    cv.notify_one();
}
int main() {
    std::thread t1(worker);
    std::thread t2(prepareWork);
    t1.join();
    t2.join();
    return 0;
}
解释:
- wait_until():等待到指定时间点,如果在这个时间点前- ready变为- true,线程会继续执行;否则超时。
输出示例:
Timeout, work is not ready.
Work is prepared, notifying worker...
总结
- std::condition_variable::wait用于协调线程之间的同步,它可以让一个线程在等待某个条件满足时进入等待状态,并通过条件变量的- notify_one()或- notify_all()来唤醒线程。
- wait()需要与- std::mutex和- std::unique_lock配合使用,以保证线程在等待期间不会造成数据竞争。
- wait_for()和- wait_until()提供了超时等待机制,允许线程在指定的时间内等待条件满足,否则超时返回。









