这几天看《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; }