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

ops/2024/9/25 21:28:04/

在 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 方法的示例:

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

进一步的示例

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

#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(资源获取即初始化)机制来实现这一点。

示例
#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 对象超出其作用域时,互斥锁会自动解锁。

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 示例
#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 类似,但它提供了更多的功能。以下是一个基本示例:

#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 标签来实现这一点:

#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 方法提前解锁互斥锁:

#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 方法尝试锁定互斥锁而不阻塞:

#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 进行延迟锁定、提前解锁和尝试锁定:

#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 的使用,可以更好地管理多线程环境下的资源同步,编写更安全和高效的代码。

http://www.ppmy.cn/ops/48859.html

相关文章

20240613日志:COPAL

Location: Beijing 1 大模型剪枝 Fig. 1.1大模型压缩-剪枝 剪枝的分类&#xff1a;结构化修剪对于简化大型语言模型和提高其效率尤其相关。非结构化修剪关注的是选择性地去除单个权重&#xff0c;旨在消除网络中不那么关键的连接。 修剪的基于阶段的分类&#xff1a;修剪可以在…

Vue3 中 props 与 emit 用法

在 Vue3 中&#xff0c;props 和 emit 的用法有所改变&#xff0c;主要的变化在于它们现在都通过 setup 函数来访问和使用。 props: props 用于父组件向子组件传递数据。在 setup 函数中&#xff0c;props 是一个参数&#xff0c;我们可以通过它访问到父组件传入的所有 prop 值…

Python跳动的爱心(双爱心版)

目录 系列文章 前言 Turtle简介 Python跳动的爱心 尾声 系列文章 序号文章目录直达链接表白系列1无法拒绝的表白界面https://want595.blog.csdn.net/article/details/1347448942满屏飘字表白代码https://want595.blog.csdn.net/article/details/1350373883无限弹窗表白代…

基于栅格占据概率和距离场的机器人覆盖轨迹模拟

基于栅格占据概率和距离场的机器人覆盖轨迹模拟 简介 辐射场模型实现 理论基础 指数函数建模 我们使用指数函数来表示机器人在某个栅格上停留时间对覆盖概率的影响: p ( t ) 1 − e − λ t p(t) 1 - e^{-\lambda t} p(t)1−e−λt 其中 λ \lambda λ 是控制增长速率…

移动硬盘打不开怎么办?原因解析!

移动硬盘是一种方便携带、快速传输大量数据的存储设备。但有时我们会遇到这样的问题&#xff1a;插上电脑后&#xff0c;移动硬盘无法打开&#xff0c;出现各种错误提示。这时候我们该怎么办呢&#xff1f; 以下是一些可能导致移动硬盘打不开的原因及解决方法&#xff1a; 1.硬…

前端HTML相关知识

1.什么是HTML HTML 指的是超文本标记语言 ( HyperText Markup Language )。 超文本:是指页面内可以包含图片、链接、声音,视频等内容 标记:标签(通过标记符号来告诉浏览器网页内容该如何显示) 浏览器根据不同的HTML标签&#xff0c;解析成我们看到的网页 2.HTML的特点 HTML不…

Java中的静态方法(static method)的特点是什么?

Java中的静态方法&#xff08;static method&#xff09;具有以下几个显著特点&#xff1a; 1.不需要实例化对象&#xff1a;静态方法可以直接通过类名调用&#xff0c;而不需要创建类的实例。这意味着静态方法属于类本身&#xff0c;而不是类的实例。 2.生命周期最长&#x…

【Qt 学习笔记】Qt窗口 | 标准对话框 | 消息对话框QMessageBox

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt窗口 | 标准对话框 | 消息对话框QMessageBox 文章编号&#xff1a;Q…