在 C++11 中,std::thread
提供了并发编程的基础设施,使得我们可以创建和管理线程。std::thread
的 detach
方法是一种常用的线程管理方式,允许线程在后台独立运行,而不必与主线程同步或等待其完成。
std::thread::detach
方法
当你调用 std::thread
对象的 detach
方法时,线程将与其创建的 std::thread
对象分离,允许它在后台继续运行。当主线程(或其他线程)不再需要等待线程完成时,可以使用 detach
方法。
调用 detach
方法后,std::thread
对象将不再与创建的线程关联,并且无法使用 join
方法来等待线程结束。如果 std::thread
对象在未调用 detach
或 join
的情况下被销毁,会导致程序终止。
示例
下面是一个使用 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
完成。
注意事项
-
无法使用
join
:一旦线程被分离,就不能再使用join
方法来等待它完成。这意味着主线程无法确保分离的线程何时完成或是否成功完成。 -
线程资源管理:分离线程在后台运行,直到它完成或程序结束。确保分离的线程在程序结束前完成,以避免可能的资源泄漏或未定义行为。
-
适用场景 :
detach
适用于那些不需要同步或等待的后台任务,例如日志记录、定时任务或其他非关键任务。 -
确保线程完成:当主线程退出时,分离的线程可能仍在运行。为了确保所有分离线程在程序结束前完成,可以使用适当的同步机制,如条件变量或其他同步手段。
进一步的示例
以下示例展示了如何使用条件变量来确保所有分离的线程在主线程完成前都已完成:
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
适用于简单的锁定场景,不需要手动管理锁的生命周期。它在以下情况下非常有用:
- 保护临界区:确保在多线程环境中对共享资源的访问是安全的。
- 异常安全 :即使在函数中抛出异常,
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
的使用,可以更好地管理多线程环境下的资源同步,编写更安全和高效的代码。