如何修复C++线程死锁示例

How to fix a C++ thread deadlock example

本文关键字:死锁 线程 何修复 C++      更新时间:2023-10-16

我设计了一个C++11线程死锁。这是通过使用具有多个线程池的两个独立函数来实现的。如何修复此示例以避免死锁?我认为解决方案与锁定程序的一致顺序有关。

#include <thread>
#include <mutex>
#include <iostream>
std::mutex kettle;
std::mutex tap;
#define THREAD_POOL 8
void kettle_tap(){
    std::cout << "Locking kettle in " << std::this_thread::get_id() << std::endl;
    // Lock the mutex kettle by creating and using lock_guard kettle_lock.
    std::lock_guard<std::mutex> kettle_lock(kettle);
    std::cout << "Locked kettle in " << std::this_thread::get_id() << std::endl;
    std::cout << "Locking tap in " << std::this_thread::get_id() << std::endl;
    // Lock the mutex tap by creating and using lock_guard tap_lock.
    std::lock_guard<std::mutex> tap_lock(tap);
    std::cout << "Locked tap in " << std::this_thread::get_id() << std::endl;
    std::cout << "Filling kettle in " << std::this_thread::get_id() << std::endl;
}
void tap_kettle(){
    std::cout << "Locking tap in " << std::this_thread::get_id() << std::endl;
    // Lock the mutex tap by creating and using lock_guard tap_lock.
    std::lock_guard<std::mutex> tap_lock(tap);
    std::cout << "Locked tap in " << std::this_thread::get_id() << std::endl;
    std::cout << "Locking kettle in " << std::this_thread::get_id() << std::endl;
    // Lock the mutex kettle by creating and using lock_guard kettle_lock.
    std::lock_guard<std::mutex> kettle_lock(kettle);
    std::cout << "Locked kettle in " << std::this_thread::get_id() << std::endl;
    std::cout << "Filling kettle in " << std::this_thread::get_id() << std::endl;
}
int main(){
    std::thread pool[THREAD_POOL];
    for (int t = 0; t < THREAD_POOL; t += 2){
        pool[t]   = std::thread(kettle_tap);
        pool[t+1] = std::thread(tap_kettle);
    }
    for (int t = 0; t < THREAD_POOL; ++t){
        pool[t].join();
    }
    std::cout << "Threads are all joined" << std::endl;
    return 0;
}

std::lock(Mutexes...)

在您的情况下,kettle_tap()tap_kettle()都应该以开头

std::lock(tap, kettle);

但是互斥参数的顺序并不重要,因此在两个函数之间可能会有不同

锁定多个互斥对象

锁定作为参数传递的所有对象,必要时阻塞调用线程。

该函数使用对对象成员lock、try_lock和unlock的未指定调用序列来锁定对象,以确保所有参数在返回时都被锁定(不会产生任何死锁)。

如果函数无法锁定所有对象(例如,因为它的一个内部调用引发了异常),则函数会在失败之前首先解锁它成功锁定的所有对象(如果有的话)。

稍后,如果您想将锁的所有权转移到std::lock_guard:

std::lock(tap, kettle);
std::lock_guard<std::mutex> kettle_lock(kettle, std::adopt_lock);
std::lock_guard<std::mutex> tap_lock(tap, std::adopt_lock);

你说得对。死锁可以通过避免循环等待来防止。在您的示例中,为了避免死锁,请在tap_kettle方法中将kettle_lock移动到tap_lock之上。这样可以得到部分排序。