【C++多线程编程】(九)之 死锁(deadlock)及如何避免
2023-12-25 10:27:41
死锁(Deadlock)是多线程或多进程并发编程中一种常见的问题,它发生在两个或多个线程(或进程)互相等待对方释放资源,导致所有参与的线程都无法继续执行的状态。
典型的死锁场景涉及多个资源和多个线程,并且每个线程都在等待其他线程释放资源。死锁的产生通常包括以下四个必要条件,也称为死锁的四个必要条件:
- 互斥条件(Mutual Exclusion): 某个资源一次只能被一个线程(或进程)占用,其他线程必须等待释放。
- 持有和等待条件(Hold and Wait): 某个线程(或进程)持有至少一个资源,并在等待获取其他资源。
- 不可抢占条件(No Preemption): 资源不能被抢占,只能在持有资源的线程(或进程)释放后才能被其他线程获取。
- 循环等待条件(Circular Wait): 存在一个等待循环,即一组线程(或进程)互相等待,形成一个循环。
当这四个条件同时满足时,就有可能导致死锁的发生。死锁是并发编程中的一种非常棘手的问题,因为它不仅会导致程序无法继续执行,还很难被检测和解决。
避免死锁的一些常用策略包括:
- 破坏互斥条件: 允许多个线程(或进程)共享资源。
- 破坏持有和等待条件: 一次性获取所有需要的资源,或者在没有足够资源时释放已经持有的资源。
- 破坏不可抢占条件: 允许抢占资源。
- 破坏循环等待条件: 给资源编号,线程(或进程)按顺序请求资源,避免循环等待。
死锁是一个需要仔细设计和管理的问题,因此在编写多线程或多进程的程序时,需要谨慎地考虑资源的获取和释放顺序,以及采取适当的同步机制来避免死锁的发生。
这个程序演示了如何使用互斥量和独占锁来保护共享资源(num_things
),以确保在多线程环境中对这些资源的访问是安全的。使用 std::unique_lock
的 std::defer_lock
参数来延迟锁定,以便稍后手动调用 std::lock
同时锁定两个互斥量,从而避免死锁的发生。
#include <mutex>
#include <thread>
#include <iostream>
// 定义一个包含数量信息和互斥量的结构体 Box
struct Box {
explicit Box(int num) : num_things{num} {}
int num_things;
std::mutex m;
};
// 定义一个转账函数,将一定数量的东西从一个 Box 转移到另一个 Box
void transfer(Box &from, Box &to, int num)
{
// 使用 std::unique_lock 来管理互斥量,但不立即上锁(defer_lock)
std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
// 同时锁定两个互斥量,以避免死锁
std::lock(lock1, lock2); // 或者使用 lock1.lock() 和 lock2.lock()
// 在临界区内进行操作,从一个 Box 中减去一定数量,同时将这些东西加到另一个 Box
from.num_things -= num;
to.num_things += num;
// 作用域结束时,std::unique_lock 的析构函数会自动解锁互斥量
}
int main()
{
// 创建两个 Box 对象,分别初始化数量
Box acc1(100);
Box acc2(50);
// 创建两个线程,分别执行 transfer 函数进行转账操作
std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);
// 等待两个线程执行结束
t1.join();
t2.join();
// 输出两个 Box 的最终数量
std::cout << "acc1 num_things: " << acc1.num_things << std::endl;
std::cout << "acc2 num_things: " << acc2.num_things << std::endl;
return 0;
}
std::ref
是 C++ 标准库中的一个函数模板,用于将一个对象包装成一个引用,从而能够在函数调用中传递引用语义而不是传值语义。,std::ref(acc1)
将 acc1
这个对象包装成一个引用,使得在 std::thread
的构造函数中能够传递引用而不是拷贝对象。如果不使用 std::ref
,则 std::thread
的构造函数默认会拷贝传递参数。
等价于
std::thread t1(transfer, &acc1, &acc2, 10);
std::thread t2(transfer, &acc2, &acc1, 5);
文章来源:https://blog.csdn.net/qq_54199287/article/details/135159208
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!