死锁在并发编程里面是很难预防的,锁和线程比较少的情况下还能通过分析代码,理出线索进行避免,一旦超过一定数量感觉就会无从下手,必须通过自动化工具进行检测。以下示例对哲学家就餐问题进行模拟,动态构建线程依赖拓扑图,并用Floyd - Warshall找环算法,找寻死锁链,在程序运行过程中如果死锁链成型,将会清晰地给出锁链信息,对下一步的死锁解除提供有力指导依据。
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <string>
#include <set>
#include <map>
#include <iostream>
#define STRINGIFY(s) #s
#define TOSTRING(s) STRINGIFY(s)
#define _CONCATE_(x,y) __FILE__##x##y
#define CONCATE(x,y) _CONCATE_(x,y)
#define LOCKNAME CONCATE(":",TOSTRING(__LINE__))
#define LOCK_INIT() {LOCKNAME, new std::mutex}
typedef struct _lock_t
{
std::mutex* mutex;
const char* name;
std::thread::id tid;
_lock_t() :_lock_t(nullptr, nullptr) {}
_lock_t(const char* _name, std::mutex* mtx) :name(_name), mutex(mtx) {}
_lock_t(_lock_t&& lk)
{
this->mutex = lk.mutex;
this->name = lk.name;
lk.mutex = nullptr;
}
_lock_t(const _lock_t& lk) = delete;
_lock_t(const _lock_t&& lk) = delete;
_lock_t& operator=(const _lock_t&) = delete;
~_lock_t()
{
if (mutex != nullptr)
{
delete mutex;
}
}
}lock_t;
using edge = std::pair<std::thread::id, std::thread::id>;
static auto* vertices = new std::set<std::thread::id>();
static auto* edges = new std::map<edge,std::string>();
static std::multimap<std::thread::id,std::string> held_locks;
static std::mutex GL;
static void check_cycles()//Floyd - Warshall 算法判断是否有环
{
for (auto v : *vertices)
for (auto u : *vertices)
for (auto w : *vertices)
{
auto uv = edges->find({ u,v });
auto vw = edges->find({ v,w });
if (uv!= edges->end() && vw!= edges->end())
edges->insert({{ u,w },uv->second+"->"+vw->second});
}
std::cout << "Lockdep check: " << std::endl;
for (auto e : *edges)
{
std::cout << " " << e.first.first<< "->" << e.first.second << std::endl;
if (e.first.first == e.first.second)
std::cout << "\033[31m!!!Warning: Cycle detected, Path: " << e.second <<"\033[0m" << std::endl;
}
}
void lock(lock_t* lk)
{
if (lk == nullptr|| lk->mutex == nullptr || lk->name == nullptr)
return;
std::thread::id tid = std::this_thread::get_id();
{
std::lock_guard<std::mutex> locker(GL);
bool update = false;
vertices->insert(tid);
//unsigned int threadId = ((_Thrd_t*) & lk->tid)->_Id;
std::thread::id lkTid = lk->tid;
auto it = held_locks.find(lkTid);
if(it != held_locks.end())//如果lk已被某个线程占有,则表明当前线程依赖了该线程
{
edges->insert({{tid,it->first},lk->name});//增加一条依赖边
update = true;
}
if (update)
check_cycles();
held_locks.insert({ tid,lk->name });
}
lk->mutex->lock();
lk->tid = tid;
}
void unlock(lock_t* lk)
{
if (lk == nullptr || lk->mutex == nullptr || lk->name == nullptr)
return;
lk->mutex->unlock();
{
std::lock_guard<std::mutex> locker(GL);
std::pair<std::multimap<std::thread::id, std::string>::iterator, std::multimap<std::thread::id, std::string>::iterator> it =
held_locks.equal_range(std::this_thread::get_id());
while (it.first != it.second)
{
if (it.first->second == std::string(lk->name))
it.first = held_locks.erase(it.first);
else
it.first++;
}
}
}
lock_t lk1 = LOCK_INIT();
lock_t lk2 = LOCK_INIT();
lock_t lk3 = LOCK_INIT();
struct some_class
{
lock_t lock;
int data;
};
void object_init(struct some_class* obj, lock_t* lk)
{
new(&obj->lock)lock_t(std::move(*lk));
obj->data = 100;
}
void create_object()
{
struct some_class* obj = new some_class;
//lock_t lk = LOCK_INIT();
//object_init(obj, &lk);
lock(&obj->lock);
unlock(&obj->lock);
delete obj;
}
std::mutex startMtx;
std::condition_variable startCond;
void T_1()
{
{
std::unique_lock<std::mutex> locker(startMtx);
startCond.wait(locker);
}
lock(&lk1);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
lock(&lk2);
create_object();
unlock(&lk1);
unlock(&lk2);
}
void T_2()
{
{
std::unique_lock<std::mutex> locker(startMtx);
startCond.wait(locker);
}
//std::this_thread::sleep_for(std::chrono::seconds(3));
lock(&lk2);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
lock(&lk3);
create_object();
unlock(&lk2);
unlock(&lk3);
}
void T_3()
{
{
std::unique_lock<std::mutex> locker(startMtx);
startCond.wait(locker);
}
//std::this_thread::sleep_for(std::chrono::seconds(6));
lock(&lk3);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
lock(&lk1);
create_object();
unlock(&lk3);
unlock(&lk1);
}
int main(int argc, char* argv[])
{
std::thread t1 = std::thread(&T_1);
std::thread t2 = std::thread(&T_2);
std::thread t3 = std::thread(&T_3);
std::cout<<"输入任意键开始测试..."<<std::endl;
getchar();
startCond.notify_all();//让3个线程同时开始竞争加锁,程序可能因为死锁问题卡死无法退出
//注意,如果线程1实在太快了,连续拿lk1和lk2都成功了,就不会死锁,如果在T_1
//拿到lk1,T_2拿到lk2,T_3拿lk3后都稍稍加点延时(10ms),这样死锁就很容易产生了。
//死锁问题在真实生产环境中也是这样,不是必现问题,很难排查。
t1.join();
t2.join();
t3.join();
system("pause");
return 0;
}