这几天看《widows核心编程》书上HandShake的代码,发现了一个问题,猜想程序可能会因此陷入死锁状态。
后经实际测试,发现这确实是一个BUG。再后来,修改了部分代码,BUG消失了。
特发出来与大家分享。有什么不对的地方,还希望大家能够指出。
widows核心编程(第4版)HandShake程序有一个BUG
如果在Request编辑框里输入字符串Server Shutdown,点击提交按钮,服务器线程会终止。
然后在编辑框里输入任意字符串,再次点击提交按钮时,由于服务器线程已经终止,所以事件
g_hevtResultReturned 不会被设置为通知状态,主线程陷入死锁。
本程序对其进行了改动。改动如下:
(1)主线程在提交请求之前,检查服务器线程是否已经终止。如果已经终止,则不进入等待函数。
否则,提交请求,并进入等待函数。
(2)为了让主线程能够检查服务器线程是否已经终止,将服务器线程句柄设置为全局变量。
(3)DialogBox返回之后,主线程仍然可能陷入死锁。原因同上。为此,添加检测服务器线程是否已经终止的代码。
如果服务器线程已经终止,则直接进入清理句柄步骤。
否则,发送字符串Server Shutdown,令其终止,待终止后再进入清理句柄步骤。
另一种修改方法是,只等待服务器线程终止,而不等待事件g_hevtResultReturned被设置为通知状态。
修改后的代码如下:
代码:
#include <windows.h>
#include <tchar.h>
#include <windowsX.h>
#include "Resource.h"
// This event is signaled when the client has a request for the server
HANDLE g_hevtRequestSubmitted;
// This event is signaled when the server has a result for the client
HANDLE g_hevtResultReturned;
// The buffer shared between the client and server threads
TCHAR g_szSharedRequestAndResultBuffer[1024];
// The special value sent from the client that causes the
// server thread to terminate cleanly.
TCHAR g_szServerShutdown[] = _T("Server Shutdown");
// 服务器线程句柄
HANDLE g_hThreadServer;
///////////////////////////////////////////////////////////////////////////////
// This is the code executed by the server thread
DWORD WINAPI ServerThread(PVOID pvParam)
{
// Assume that the server thread is to run forever
BOOL fShutdown = FALSE;
while ( !fShutdown )
{
// Wait for the client to submit a request
WaitForSingleObject( g_hevtRequestSubmitted, INFINITE );
// Check to see if the client wants the server to terminate
fShutdown =
(lstrcmpi( g_szSharedRequestAndResultBuffer, g_szServerShutdown ) == 0);
if (!fShutdown)
{
// Process the client's request (reverse the string)
_tcsrev( g_szSharedRequestAndResultBuffer );
}
// Let the client process the request's result
SetEvent( g_hevtResultReturned );
}
// The client wants us to shutdown, exit
return 0;
}
///////////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
SendMessage( hwnd, WM_SETICON, TRUE, (LPARAM)
LoadIcon( (HINSTANCE)hwnd, MAKEINTRESOURCE(IDI_HANDSHAKE) ) );
// Initialize the edit control with some test data request
Edit_SetText( GetDlgItem( hwnd, IDC_REQUEST ), _T("Some test data") );
return(TRUE);
}
///////////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
switch (id)
{
case IDCANCEL:
EndDialog( hwnd, id );
break;
case IDC_SUBMIT: // Submit a request to the server thread
//检查服务器线程是否已经终止
DWORD ExitCode;
GetExitCodeThread( g_hThreadServer, &ExitCode );
if ( ExitCode!=STILL_ACTIVE )
{
break;
}
// Copy the request string into the shared data buffer
Edit_GetText( GetDlgItem(hwnd, IDC_REQUEST),
g_szSharedRequestAndResultBuffer,
sizeof(g_szSharedRequestAndResultBuffer) );
// Let the server thread know that a request is ready in the buffer
SetEvent( g_hevtRequestSubmitted );
// Wait for the server to process the request and give us the result
WaitForSingleObject( g_hevtResultReturned, INFINITE );/*如果用户已经发送过终止字符串,则g_hevtResultReturned不会变为通知状态*/
// Let the user know the result
Edit_SetText( GetDlgItem(hwnd, IDC_RESULT),
g_szSharedRequestAndResultBuffer );
break;
}
}
///////////////////////////////////////////////////////////////////////////////
int WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
HANDLE_MSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
HANDLE_MSG(hwnd, WM_COMMAND, Dlg_OnCommand);
}
return(FALSE);
}
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )
{
// Create & initialize the 2 nonsignaled, auto-reset events
g_hevtRequestSubmitted = CreateEvent( NULL, FALSE, FALSE, NULL );
g_hevtResultReturned = CreateEvent( NULL, FALSE, FALSE, NULL );
// Spawn the server thread
DWORD dwThreadID;
g_hThreadServer = CreateThread( NULL, 0, ServerThread, NULL, 0, &dwThreadID );
// Execute the client thread's user-interface
DialogBox( hInstance, MAKEINTRESOURCE(IDD_HANDSHAKE), NULL, Dlg_Proc );
// 检查服务器线程是否已经终止
DWORD ExitCode;
GetExitCodeThread( g_hThreadServer, &ExitCode );
if ( ExitCode!=STILL_ACTIVE )
{
goto _clean_up_everything;
}
// The client's UI is closing, have the server thread shutdown
lstrcpy( g_szSharedRequestAndResultBuffer, g_szServerShutdown );
SetEvent( g_hevtRequestSubmitted );
// Wait for the server thread to acknowledge the shutdown AND
// wait for the server thread to fully terminate
HANDLE h[2];
h[0] = g_hevtResultReturned;
h[1] = g_hThreadServer;
WaitForMultipleObjects( 2, h, TRUE, INFINITE );/*如果用户已经发送过终止字符串,则g_hevtResultReturned不会变为通知状态*/
//添加一个标签
_clean_up_everything:
// Properly clean up everything
CloseHandle( g_hThreadServer );
CloseHandle( g_hevtRequestSubmitted );
CloseHandle( g_hevtResultReturned );
// The client thread terminates with the whole process
return 0;
}