1. 程式人生 > >C++死鎖解決心得

C++死鎖解決心得

一、概述
C++多執行緒開發中,容易出現死鎖導致程式掛起的現象。
關於死鎖的資訊,見百度百科http://baike.baidu.com/view/121723.htm

解決步驟分為三步:
1、檢測死鎖執行緒。
2、列印執行緒資訊。
3、修改死鎖程式。

二、程式示例
VS2005建立支援MFC的win32控制檯程式。
程式碼見示例程式碼DeadLockTest.cpp。
// DeadLockTest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "DeadLockTest.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// The one and only application object

CWinApp theApp;

using namespace std;

CRITICAL_SECTION cs1;
CRITICAL_SECTION cs2;
CRITICAL_SECTION csprint;

//初始化關鍵程式碼段
void InitMyCriticalSection();
//刪除關鍵程式碼段
void DeleteMyCriticalSection();
//列印資訊
void PrintString(const CString& strInfo);

DWORD WINAPI Thread1(LPVOID lpParameter);
DWORD WINAPI Thread2(LPVOID lpParameter);

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    int nRetCode = 0;

    // initialize MFC and print and error on failure
    if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
    {
        // TODO: change error code to suit your needs
        _tprintf(_T("Fatal Error: MFC initialization failed\n"));
        nRetCode = 1;

        return nRetCode;
    }

    //初始化關鍵程式碼段
    InitMyCriticalSection();

    //建立執行緒
    HANDLE hThread1 = CreateThread(NULL, 0, Thread1, NULL, 0, NULL);
    HANDLE hThread2 = CreateThread(NULL, 0, Thread2, NULL, 0, NULL);

    //等待執行緒結束
    WaitForSingleObject(hThread1, INFINITE);
    WaitForSingleObject(hThread2, INFINITE);

    //關閉執行緒控制代碼
    CloseHandle(hThread1);
    CloseHandle(hThread2);

    //釋放關鍵程式碼段
    DeleteMyCriticalSection();

    return nRetCode;
}

void InitMyCriticalSection()
{
    InitializeCriticalSection(&cs1);
    InitializeCriticalSection(&cs2);
    InitializeCriticalSection(&csprint);
}

void DeleteMyCriticalSection()
{
    DeleteCriticalSection(&cs1);
    DeleteCriticalSection(&cs2);
    DeleteCriticalSection(&csprint);
}

DWORD WINAPI Thread1(LPVOID lpParameter)
{
    for (int i = 0; i < 5; i++)
    {
        EnterCriticalSection(&cs1);
        Sleep(500);
        EnterCriticalSection(&cs2);

        PrintString(_T("Thread1"));

        LeaveCriticalSection(&cs2);
        LeaveCriticalSection(&cs1);
    }

    return 1;
}

DWORD WINAPI Thread2(LPVOID lpParameter)
{
    for (int i = 0; i < 5; i++)
    {
        EnterCriticalSection(&cs2);
        Sleep(500);
        EnterCriticalSection(&cs1);

        PrintString(_T("Thread2"));

        LeaveCriticalSection(&cs1);
        LeaveCriticalSection(&cs2);
    }

    return 1;
}

void PrintString(const CString& strInfo)
{
    EnterCriticalSection(&csprint);
    wcout<<(const TCHAR*)strInfo<<endl;
    LeaveCriticalSection(&csprint);
}

執行DeadLockTest.exe,程式掛起。

三、死鎖檢測
檢測工具見《Windows核心程式設計》,第9章9.8.6節LockCop檢測工具。
工具原始碼地址:http://www1.wintellect.com/Resources/Details/86

LockCop可使用vs2010編譯成功。
備註:該工具使用了Windows Vista/ 7提供的WCT API,故需要在Windows Vista/ 7系統執行LockCop檢測工具。

檢測,掛起的DeadLockTest.exe,得到執行緒資訊。

檢測到程式掛起由死鎖引起。

執行緒4014:等待執行緒772、執行緒4012完成。
執行緒772:擁有關鍵程式碼段A,等待關鍵程式碼段B(被執行緒4012擁有)。
執行緒4012:擁有關鍵程式碼段B,等待關鍵程式碼段A(被執行緒772擁有)。

執行緒772與4012互相等待,程式發生死鎖現象。

四、列印資訊
為了便於查詢問題,我們加上執行緒列印資訊。
列印執行緒名稱、執行緒ID以及關鍵程式碼段進入資訊。

DWORD WINAPI Thread1(LPVOID lpParameter)
{
    CString strThreadID = _T("");
    strThreadID.Format(_T("%d"), GetCurrentThreadId());

    CString strPrintInfo = _T("");

    for (int i = 0; i < 5; i++)
    {
        EnterCriticalSection(&cs1);

        strPrintInfo = _T("");
        strPrintInfo += _T("Thread1 ");
        strPrintInfo += strThreadID;
        strPrintInfo += _T(" EnterCriticalSection(&cs1)");

        PrintString(strPrintInfo);

        Sleep(500);
        EnterCriticalSection(&cs2);

        strPrintInfo = _T("");
        strPrintInfo += _T("Thread1 ");
        strPrintInfo += strThreadID;
        strPrintInfo += _T(" EnterCriticalSection(&cs2)");

        PrintString(strPrintInfo);

        LeaveCriticalSection(&cs2);
        LeaveCriticalSection(&cs1);
    }

    return 1;
}

DWORD WINAPI Thread2(LPVOID lpParameter)
{
    CString strThreadID = _T("");
    strThreadID.Format(_T("%d"), GetCurrentThreadId());

    CString strPrintInfo = _T("");

    for (int i = 0; i < 5; i++)
    {
        EnterCriticalSection(&cs2);

        strPrintInfo = _T("");
        strPrintInfo += _T("Thread2 ");
        strPrintInfo += strThreadID;
        strPrintInfo += _T(" EnterCriticalSection(&cs2)");

        PrintString(strPrintInfo);

        Sleep(500);

        EnterCriticalSection(&cs1);

        strPrintInfo = _T("");
        strPrintInfo += _T("Thread2 ");
        strPrintInfo += strThreadID;
        strPrintInfo += _T(" EnterCriticalSection(&cs1)");

        PrintString(strPrintInfo);

        LeaveCriticalSection(&cs1);
        LeaveCriticalSection(&cs2);
    }

    return 1;
}

執行結果如下。


五、死鎖修改
執行緒互斥進行修改,Thread1與Thread2對關鍵程式碼段的進入與退出順序改為相同。程式執行正常。
修改後執行緒程式碼。

DWORD WINAPI Thread1(LPVOID lpParameter)
{
    for (int i = 0; i < 5; i++)
    {
        EnterCriticalSection(&cs1);
        Sleep(500);
        EnterCriticalSection(&cs2);

        PrintString(_T("Thread1"));

        LeaveCriticalSection(&cs2);
        LeaveCriticalSection(&cs1);
    }

    return 1;
}

DWORD WINAPI Thread2(LPVOID lpParameter)
{
    for (int i = 0; i < 5; i++)
    {
        EnterCriticalSection(&cs1);
        Sleep(500);
        EnterCriticalSection(&cs2);

        PrintString(_T("Thread2"));

        LeaveCriticalSection(&cs2);
        LeaveCriticalSection(&cs1);
    }

    return 1;
}