【发布时间】:2013-08-12 22:26:09
【问题描述】:
我有一个带有按钮的对话框和一些其他控件。
当按下该按钮时,会产生一个工作线程。
为了便于讨论,我们只说线程函数做了很长的工作。
每次单击按钮时,都会产生新线程并执行其工作。
在工作线程执行其工作时不应阻止对话框,因为用户应该能够将其最小化,单击其他控件等等。
在维基百科上,我找到了一个术语无锁算法,它指的是非阻塞线程同步。
这就是我对非阻塞线程同步感兴趣的原因。我相信这将确保我需要的行为。
我是多线程的新手,但我确实在这里找到了一些关于它的文章/已回答的问题,并且已经阅读了有关它的 Microsoft 文档。
其中大多数使用以下算法:
1.
主线程声明一个 volatile 变量(通常是 int 或 bool )并将其传递给工作线程。工作线程在循环中检查变量,如果未设置为指示其终止,则继续执行其工作。当需要终止时,父线程设置变量。
2.
Microsoft 网站上还有大量关于同步的文档。在那里,我发现了互斥锁、临界区、互锁等等。
这些问题是它们总是阻塞父线程(通常使用 WaitForSingleObject 或 WaitForMultipleObjects API),直到工作线程完成。
此外,在这里搜索,我发现了一个帮助我开始的最近的问题 (Abort thread properly when dialog box close button is clicked)。
为了解决我的问题,我将把这个问题分成多个,分别发布,以尊重 StackOverflow 的规则。
所以我现在的第一个问题是:
我应该使用哪种 API/算法来实现非阻塞线程同步,这将有助于我实现对话框的行为如上所述?
如果可能的话,我还希望提供指向教程或代码示例的链接,因为我在 Google 上不走运(此外,开发人员通过代码/伪代码学得最好)。
我将在下面使用这些代码 sn-ps 展示我的初步尝试,以防它们被证明是有用的:
// thread function
DWORD WINAPI MyThread()
{
int result = 0;
// do something
if( /** everything is OK **/ )
return result;
else
{
result = 1;
return result;
}
}
现在对话框:
// button clicked handler
case IDC_BUTTON1:
{
// create thread
DWORD tID;
HANDLE th = CreateThread( NULL , 0 ,
(LPTHREAD_START_ROUTINE)MyThread ,
NULL , 0 , &tID );
if( !th )
EndDialog( hWnd, IDCANCEL );
CloseHandle( th );
}
break;
case IDCANCEL:
EndDialog( hWnd, IDCANCEL );
break;
此时,当我运行程序时,激活对话框,然后单击按钮,工作线程就会产生,如果我等待它完成就好了。
但是,如果我提前关闭对话框,我的问题的性质与我在此处找到的问题相同(我认为 IDCANCEL 处理程序中的 WaitForMultipleObjects 可以部分解决此问题,但我将把它留到另一篇文章中)。
编辑#1:
我发布了新代码,以反映我的进度和遇到的问题。
重要提示:
我没有启动多个线程,而是隐藏了启动线程的按钮,一旦按下它。
这意味着用户必须等待第一个线程完成,然后才能激活第二个线程。
这样做是为了便于调试。
定义自定义消息以指示线程是否正常退出或发生错误。
#define WM_THREAD_OK ( WM_APP + 1 )
#define WM_THREAD_ERROR ( WM_APP + 2 )
添加将传递给线程的数据结构,以便它可以与对话框通信,因此可以正确中止
struct Data
{
HWND hwnd;
bool bContinue;
};
重新设计的线程函数将消息发送到对话框,以便它可以通知对话框有关错误或优雅结束。
新代码(这是基于 Charles Petzold-Programming Windows 5th ed. 书中的示例。):
// thread function
DWORD WINAPI MyThread( LPVOID lpvoid )
{
HRESULT hr;
volatile Data *data = ( Data* )lpvoid;
try
{
for( int i = 0; i < 10 && data->bContinue; i++ )
{
hr = // do something
if( FAILED(hr) )
throw _com_error(hr);
}
// once you leave loop, check if thread was aborted, or all was well
if( ! ( data->bContinue ) )
{
// thread was aborted, do cleanup and exit
// cleanup
return 0;
}
// if all went well, do cleanup, and "say" so to the dialog box
// do cleanup here
SendMessage( data->hwnd, WM_THREAD_OK, 0, 0 );
return 0; // exit gracefully
}
catch( _com_error & )
{
// do cleanup
SendMessage( data->hwnd, WM_THREAD_ERROR, 0, 0 );
return 1;
}
}
这是对话框的新代码(注意:对话框现在是无模式的):
static Data data;
HANDLE th = NULL;
// button clicked handler
case IDC_BUTTON1:
{
// create thread
DWORD tID;
th = CreateThread( NULL , 0 ,
(LPTHREAD_START_ROUTINE)MyThread ,
(LPVOID)&data, 0 , &tID );
if( !th )
DestroyWindow( hwnd );
// hide the button which activates thread
ShowWindow( GetDlgItem( hwnd, IDC_BUTTON1 ), SW_HIDE );
}
break;
case WM_THREAD_OK:
if( th )
CloseHandle( th );
ShowWindow( GetDlgItem( hwnd, IDC_BUTTON1 ), SW_SHOW );
break;
case WM_THREAD_ERROR:
if( th )
CloseHandle( threadHandle );
MessageBox( hwnd, L"Error", L"Error", MB_ICONERROR );
break;
case IDCANCEL:
data.bContinue = false; // set thread abortion flag
if( th )
{
// after I comment out below statement, thread aborts properly
// WaitForSingleObject( th, INFINITE );
CloseHandle( th );
}
DestroyWindow( hwnd ); // dialog box is now modeless one!
break;
我的问题是:这段代码有错误吗?是否可以改进,如何改进?
谢谢。
【问题讨论】:
-
参见 MSDN 上的 PostMessage()。 0-wait answer/s 的轮询是没有希望的。
-
有很多方法可以做到这一点。在调用
EndDialog()之前等待all 线程句柄将是最直接的解决方案。您目前在创建线程后立即关闭您的线程句柄,因此必须进行更改(但您仍想关闭它们,只需在收到信号后终止)。将每个线程保持在一个连续的区域(例如std::vector<HANDLE>可能会让您更轻松(如果您使用WaitForMultipleObjects(),则需要这样做,但同样,不要忘记在 Win32 端进行适当的清理。 -
谢谢。你提到有很多方法可以做到这一点,所以我问你这个:你能提供一些小的sn-p/好的教程/算法,可以帮助像我这样的初学者开始解决这个问题吗?再次感谢您的回答。
-
如果您是多线程的初学者,请暂时远离无锁算法。它们比您需要的要先进得多,而且非常容易错误地实施。
-
简单地删除 volatile 不是答案。我的建议是用真正的同步机制替换 volatile
标签: c++ multithreading winapi