【知识点】std::thread::detach std::lock_guard std::unique_lock

在 C++11 中,std::thread 提供了并发编程的基础设施,使得我们可以创建和管理线程。std::threaddetach 方法是一种常用的线程管理方式,允许线程在后台独立运行,而不必与主线程同步或等待其完成。

std::thread::detach 方法

当你调用 std::thread 对象的 detach 方法时,线程将与其创建的 std::thread 对象分离,允许它在后台继续运行。当主线程(或其他线程)不再需要等待线程完成时,可以使用 detach 方法。

调用 detach 方法后,std::thread 对象将不再与创建的线程关联,并且无法使用 join 方法来等待线程结束。如果 std::thread 对象在未调用 detachjoin 的情况下被销毁,会导致程序终止。

示例

下面是一个使用 std::thread::detach 方法的示例:

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

void backgroundTask() {
    for (int i = 0; i < 5; ++i) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "Background task running: " << i + 1 << " seconds\n";
    }
    std::cout << "Background task completed.\n";
}

int main() {
    std::thread t(backgroundTask);  // 创建一个线程运行 backgroundTask 函数

    t.detach();  // 分离线程

    std::cout << "Main thread continues to run...\n";
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout << "Main thread completed.\n";

    return 0;
}

在这个示例中,backgroundTask 函数将在后台运行 5 秒,每秒打印一次消息。当主线程调用 t.detach() 分离线程后,它继续执行其自身的操作,不再等待 backgroundTask 完成。

注意事项

  1. 无法使用 join :一旦线程被分离,就不能再使用 join 方法来等待它完成。这意味着主线程无法确保分离的线程何时完成或是否成功完成。

  2. 线程资源管理:分离线程在后台运行,直到它完成或程序结束。确保分离的线程在程序结束前完成,以避免可能的资源泄漏或未定义行为。

  3. 适用场景detach 适用于那些不需要同步或等待的后台任务,例如日志记录、定时任务或其他非关键任务。

  4. 确保线程完成:当主线程退出时,分离的线程可能仍在运行。为了确保所有分离线程在程序结束前完成,可以使用适当的同步机制,如条件变量或其他同步手段。

进一步的示例

以下示例展示了如何使用条件变量来确保所有分离的线程在主线程完成前都已完成:

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

std::mutex mtx;
std::condition_variable cv;
bool done = false;

void backgroundTask(int id) {
    std::this_thread::sleep_for(std::chrono::seconds(id));
    std::cout << "Thread " << id << " completed.\n";
    std::unique_lock<std::mutex> lock(mtx);
    done = true;
    cv.notify_all();
}

int main() {
    std::vector<std::thread> threads;

    for (int i = 1; i <= 5; ++i) {
        std::thread t(backgroundTask, i);
        t.detach();
    }

    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return done; });

    std::cout << "All threads completed.\n";

    return 0;
}

在这个示例中,使用条件变量 cv 和互斥锁 mtx 来确保所有分离的线程在主线程完成前都已完成。当一个分离线程完成其任务时,它通知主线程更新状态。

总结

std::thread::detach 方法使得线程在后台独立运行,主线程无需等待其完成。这种方法适用于不需要同步或等待的后台任务,但需要注意管理线程的生命周期和资源,以确保程序正常运行。

std::lock_guard 是 C++11 引入的一个便利类,用于在作用域内自动管理互斥锁的锁定和解锁。它主要用于确保在函数或代码块执行期间互斥锁被正确地锁定和解锁,即使在异常或提前返回的情况下,也能够确保互斥锁被正确释放。

std::lock_guard

基本用法

std::lock_guard 的主要目的是提供一种简单的方式来确保互斥锁在作用域结束时自动解锁。它通过 RAII(资源获取即初始化)机制来实现这一点。

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

std::mutex mtx;

void print_thread_id(int id) {
    std::lock_guard<std::mutex> lock(mtx);  // 锁定互斥锁
    std::cout << "Thread ID: " << id << std::endl;
    // 在作用域结束时,互斥锁会自动解锁
}

int main() {
    std::thread t1(print_thread_id, 1);
    std::thread t2(print_thread_id, 2);

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

    return 0;
}

在这个示例中,std::lock_guard 确保在 print_thread_id 函数内互斥锁 mtx 被锁定,并在函数结束时自动解锁。

工作原理

std::lock_guard 的构造函数接受一个互斥锁对象,并在构造时锁定互斥锁。它的析构函数会自动解锁互斥锁,因此,当 std::lock_guard 对象超出其作用域时,互斥锁会自动解锁。

cpp 复制代码
template <typename Mutex>
class lock_guard {
public:
    explicit lock_guard(Mutex& m) : mutex(m) {
        mutex.lock();
    }

    ~lock_guard() {
        mutex.unlock();
    }

    lock_guard(const lock_guard&) = delete;
    lock_guard& operator=(const lock_guard&) = delete;

private:
    Mutex& mutex;
};

使用场景

std::lock_guard 适用于简单的锁定场景,不需要手动管理锁的生命周期。它在以下情况下非常有用:

  1. 保护临界区:确保在多线程环境中对共享资源的访问是安全的。
  2. 异常安全 :即使在函数中抛出异常,std::lock_guard 也能确保互斥锁被正确解锁。

std::unique_lock 的比较

std::unique_lock 提供了更灵活的锁管理功能,例如延迟锁定、提前解锁、再次锁定等。相比之下,std::lock_guard 更简单,但功能也更有限。

std::unique_lock 示例
cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void print_thread_id(int id) {
    std::unique_lock<std::mutex> lock(mtx);  // 锁定互斥锁
    std::cout << "Thread ID: " << id << std::endl;
    // 可选:提前解锁
    lock.unlock();
    // 可选:再次锁定
    lock.lock();
    // 在作用域结束时,互斥锁会自动解锁
}

int main() {
    std::thread t1(print_thread_id, 1);
    std::thread t2(print_thread_id, 2);

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

    return 0;
}

在这个示例中,std::unique_lock 提供了更多的灵活性,可以提前解锁和再次锁定互斥锁。

总结

  • std::lock_guard 是一个简单的 RAII 机制,用于在作用域内自动管理互斥锁的锁定和解锁。
  • 它适用于简单的锁定场景,确保在异常或提前返回的情况下互斥锁被正确释放。
  • 对于需要更灵活锁管理的情况,可以使用 std::unique_lock

std::unique_lock

std::unique_lock 是 C++11 引入的一个模板类,用于管理互斥锁的锁定和解锁。与 std::lock_guard 相比,std::unique_lock 提供了更强大的功能和更灵活的锁管理选项。它允许延迟锁定、提前解锁、再次锁定等操作,是一种更通用的互斥锁管理方式。

std::unique_lock 的功能和用法

基本用法

std::unique_lock 的基本用法与 std::lock_guard 类似,但它提供了更多的功能。以下是一个基本示例:

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

std::mutex mtx;

void print_thread_id(int id) {
    std::unique_lock<std::mutex> lock(mtx);  // 锁定互斥锁
    std::cout << "Thread ID: " << id << std::endl;
    // 在作用域结束时,互斥锁会自动解锁
}

int main() {
    std::thread t1(print_thread_id, 1);
    std::thread t2(print_thread_id, 2);

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

    return 0;
}

在这个示例中,std::unique_lock 确保在 print_thread_id 函数内互斥锁 mtx 被锁定,并在函数结束时自动解锁。

延迟锁定

std::unique_lock 允许延迟锁定互斥锁,即在创建 std::unique_lock 对象时不立即锁定互斥锁。可以使用 defer_lock 标签来实现这一点:

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

std::mutex mtx;

void print_thread_id(int id) {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);  // 不立即锁定互斥锁
    // 需要时手动锁定
    lock.lock();
    std::cout << "Thread ID: " << id << std::endl;
    // 自动解锁
}

int main() {
    std::thread t1(print_thread_id, 1);
    std::thread t2(print_thread_id, 2);

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

    return 0;
}

在这个示例中,互斥锁在创建 std::unique_lock 对象时并未被锁定,而是在需要时手动锁定。

提前解锁

可以使用 unlock 方法提前解锁互斥锁:

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

std::mutex mtx;

void print_thread_id(int id) {
    std::unique_lock<std::mutex> lock(mtx);  // 锁定互斥锁
    std::cout << "Thread ID: " << id << std::endl;
    lock.unlock();  // 提前解锁
    // 其他不需要锁定的操作
}

int main() {
    std::thread t1(print_thread_id, 1);
    std::thread t2(print_thread_id, 2);

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

    return 0;
}

在这个示例中,互斥锁在 print_thread_id 函数内提前解锁,以便进行其他不需要锁定的操作。

试图锁定

可以使用 try_lock 方法尝试锁定互斥锁而不阻塞:

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

std::mutex mtx;

void print_thread_id(int id) {
    std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);  // 尝试锁定互斥锁
    if (lock.owns_lock()) {
        std::cout << "Thread " << id << " successfully locked the mutex." << std::endl;
    } else {
        std::cout << "Thread " << id << " failed to lock the mutex." << std::endl;
    }
}

int main() {
    std::thread t1(print_thread_id, 1);
    std::thread t2(print_thread_id, 2);

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

    return 0;
}

在这个示例中,try_lock 方法尝试锁定互斥锁。如果锁定成功,owns_lock 方法将返回 true;否则返回 false

std::unique_lock 的构造函数

std::unique_lock 有多个构造函数,可以用于不同的锁定策略:

  • 默认构造:不锁定互斥锁。
  • 立即锁定构造:在创建时立即锁定互斥锁。
  • 延迟锁定构造:在创建时不锁定互斥锁,可以稍后手动锁定。
  • 尝试锁定构造:尝试锁定互斥锁。

详细示例

下面是一个更详细的示例,展示了如何使用 std::unique_lock 进行延迟锁定、提前解锁和尝试锁定:

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

std::mutex mtx;

void worker(int id) {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);  // 延迟锁定
    std::cout << "Thread " << id << " is waiting to lock the mutex..." << std::endl;

    // 尝试锁定
    if (lock.try_lock()) {
        std::cout << "Thread " << id << " has locked the mutex." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lock.unlock();
        std::cout << "Thread " << id << " has unlocked the mutex." << std::endl;
    } else {
        std::cout << "Thread " << id << " failed to lock the mutex." << std::endl;
    }
}

int main() {
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);

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

    return 0;
}

在这个示例中,两个线程都尝试锁定同一个互斥锁。使用 std::unique_lock 的延迟锁定和尝试锁定机制,线程可以在失败时进行不同的处理。

总结

  • std::unique_lock 提供了更灵活的锁管理功能,包括延迟锁定、提前解锁和尝试锁定。
  • 它比 std::lock_guard 更强大,但也更复杂,适用于需要灵活锁管理的场景。
  • 通过理解 std::unique_lock 的使用,可以更好地管理多线程环境下的资源同步,编写更安全和高效的代码。
相关推荐
ragnwang2 小时前
C++ Eigen常见的高级用法 [学习笔记]
c++·笔记·学习
lqqjuly5 小时前
特殊的“Undefined Reference xxx“编译错误
c语言·c++
冰红茶兑滴水6 小时前
云备份项目--工具类编写
linux·c++
刘好念6 小时前
[OpenGL]使用 Compute Shader 实现矩阵点乘
c++·计算机图形学·opengl·glsl
酒鬼猿6 小时前
C++进阶(二)--面向对象--继承
java·开发语言·c++
姚先生977 小时前
LeetCode 209. 长度最小的子数组 (C++实现)
c++·算法·leetcode
小王爱吃月亮糖7 小时前
QT开发【常用控件1】-Layouts & Spacers
开发语言·前端·c++·qt·visual studio
aworkholic8 小时前
opencv sdk for java中提示无stiching模块接口的问题
java·c++·opencv·jni·opencv4android·stiching
程序员老冯头8 小时前
第十六章 C++ 字符串
开发语言·c++
Xenia2238 小时前
复习篇~第二章程序设计基础
c++·算法