首页 > 编程语言 >C++之死锁

C++之死锁

时间:2023-07-08 11:22:25浏览次数:36  
标签:std lock C++ 互斥 死锁 mtx2 mtx1

背景

在多线程编程中,死锁是一个常见的问题,它会导致程序陷入无法继续执行的状态。在这篇博客中,我们将介绍C++中死锁的概念、产生原因以及解决办法。

什么是死锁?

死锁是指多个线程在等待对方释放资源,导致彼此都无法继续执行的情况。死锁通常发生在多个线程同时锁定多个互斥锁的情况下。以下是一个简单的死锁示例:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx1;
std::mutex mtx2;

void thread1() {
    std::unique_lock<std::mutex> lock1(mtx1);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::unique_lock<std::mutex> lock2(mtx2);
    std::cout << "Thread 1 finished." << std::endl;
}

void thread2() {
    std::unique_lock<std::mutex> lock2(mtx2);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::unique_lock<std::mutex> lock1(mtx1);
    std::cout << "Thread 2 finished." << std::endl;
}

int main() {
    std::thread t1(thread1);
    std::thread t2(thread2);
    t1.join();
    t2.join();
    return 0;
}

在这个例子中,thread1函数先锁定mtx1,然后尝试锁定mtx2;thread2函数先锁定mtx2,然后尝试锁定mtx1。由于两个线程都在等待对方释放互斥锁,导致死锁发生。

死锁的四个必要条件

死锁发生需要满足以下四个必要条件:

  • 互斥条件:一个资源在某一时刻只能被一个线程占用。
  • 请求与保持条件:一个线程在请求其他资源时,保持对已分配资源的占用。
  • 不可抢占条件:一个资源只能被占用的线程主动释放,其他线程不能强行抢占。
  • 循环等待条件:存在一个线程等待资源的循环链,链中的每个线程都在等待下一个线程占用的资源。 只有在这四个条件同时满足时,死锁才会发生。因此,要解决死锁问题,我们需要破坏这四个条件中的至少一个。

解决死锁的方法

按照固定的顺序加锁

确保所有线程在加锁时遵循相同的顺序,这样可以避免循环等待导致的死锁。例如,在上述死锁示例中,我们可以修改thread2函数,使其先锁定mtx1,然后锁定mtx2。

使用std::lock()一次性加锁多个互斥锁

std::lock()函数可以保证在没有死锁的情况下一次性锁定多个互斥锁。例如,当我们需要锁定两个互斥锁mtx1和mtx2时,我们可以使用以下代码:

std::lock(mtx1, mtx2);
使用std::try_lock()尝试加锁

std::try_lock()函数尝试加锁,如果加锁失败,则立即返回。这样我们可以在加锁失败时执行其他操作,避免死锁。例如,我们可以修改上述死锁示例中的thread1和thread2函数,使用std::try_lock()尝试加锁:

void thread1() {
    while (true) {
        if (mtx1.try_lock()) {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            if (mtx2.try_lock()) {
                std::cout << "Thread 1 finished." << std::endl;
                mtx2.unlock();
                mtx1.unlock();
                break;
            }
            mtx1.unlock();
        }
    }
}

void thread2() {
    while (true) {
        if (mtx2.try_lock()) {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            if (mtx1.try_lock()) {
                std::cout << "Thread 2 finished." << std::endl;
                mtx1.unlock();
                mtx2.unlock();
                break;
            }
            mtx2.unlock();
        }
    }
}

在这个修改后的例子中,thread1和thread2函数使用std::try_lock()尝试加锁。如果加锁失败,它们会立即释放已经占用的互斥锁,然后继续尝试加锁。这样可以避免死锁问题。

避免嵌套锁

尽量避免在已经锁定一个互斥锁的情况下再锁定其他互斥锁。如果必须使用嵌套锁,可以考虑将不同的操作分离到不同的函数中,并在函数调用之间解锁互斥锁。

使用锁的分层

为互斥锁分配层次,并确保在较低层次的互斥锁解锁后才能锁定较高层次的互斥锁。这样可以减少死锁发生的可能性。

最后

总结一下,在这篇博客中,我们介绍了C++中死锁的概念、产生原因以及解决办法。通过遵循一定的准则和技巧,我们可以有效地避免死锁问题,确保多线程程序的正确性和稳定性。在进行多线程编程时,我们需要时刻关注死锁等潜在风险,并采取相应的措施来防范和解决这些问题。

 

标签:std,lock,C++,互斥,死锁,mtx2,mtx1
From: https://www.cnblogs.com/blizzard8204/p/17536947.html

相关文章

  • C++之线程管控(一)
    背景多线程编程在实际应用中非常常见,它可以帮助我们提高程序性能,实现高效的任务调度。从C++11开始,C++语言已经提供了对多线程编程的原生支持。本文将详细介绍如何使用C++进行线程管控,包括发起线程、等待线程完成、异常处理以及在后台运行线程等内容。发起线程C++11提供了一个名......
  • C++之线程管控(二)
    背景随着多核处理器的普及,多线程编程已经成为软件开发中不可或缺的一部分。C++11标准为我们带来了线程库,让我们能够更方便地在C++中实现多线程编程。在这篇博客中,我们将介绍C++线程管控的基本概念和方法,包括向线程函数传递参数,移交线程归属权,运行时选择线程数量和识别线程。向线......
  • C++ Primer 学习笔记——第七章
    第七章类前言基本数据类型有时候并不能解决某些特定问题,而通过自定义的类就可以通过理解问题概念,使得程序更加容易编写、调试和修改。类的基本思想是数据抽象(dataabstraction)和封装(encapsulation)。数据抽象是一种依赖于接口(interface)和实现(implementation)分离的编程(以及设......
  • c++ 科幻版 沙漠神殿2
    #include<iostream>#include"minecraft.h"#include<string>usingnamespacestd;TxMinecraftmc;intx,y,z;boolcon;boollianjie(){ returncon=mc.ConnectMinecraft("mc.makeblock.net.cn","a9d44e758f6e4cf8b2da26241......
  • 请使用C++计算出2^2023与3^2023的和
    易知,这个和的数字是非常大的,大到longlong都装不下,这个时候如果使用longlong是无法进行运算的。欸!这会高精度算法(即大数运算)就开始发光发热了。以下是我看资料总结的一些歪瓜裂枣。对于一位高精度数字,用整数数组存储,数组每一个元素对应一位十进制数,由其下标顺序指明位序号......
  • c++沙漠神殿
    #include<iostream>#include"minecraft.h"#include<string>usingnamespacestd;TxMinecraftmc;intx=0,y=0,z=0;intmain(intargc,char**argv){boolcon=mc.ConnectMinecraft("zk.makeblock.net.cn","a9d44e758f6e4cf8b......
  • C++黑马程序员——P193-196. string容器 字符串比较,字符存取,字符串插入和删除,子串
    P193.string容器——字符串比较P194....——字符存取P195....——字符串插入和删除P196....——子串获取P193.字符串比较 ——————————————————————————————————————————————————————————1//字符......
  • C++黑马程序员——P189-192. string容器 构造函数,赋值,拼接,查找和替换
    P189.string容器——构造函数P190....——赋值操作P191....——字符串拼接P192....——字符串查找和替换P189.构造函数———————————————————————————————————————————————————————————————......
  • C++ 设计模式之建造者模式
    设计模式之建造者模式建造者模式,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。其UML图如下:简单理解就是Builder中定义了创建Product各个部分的接口。ConcreteBuilder中具体实现了创建Product中的各个部分的接口,就是具体的建造者。Director......
  • C++设计模式之观察者模式
    设计模式之观察者模式观察者模式定义了一种一对多的依赖关系,让多个观察者同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。其UML图如下: 在ConcretSubject内部有一个Observer的列表,当Subject的状态发生改变时,会通知列表内......