标签:
杂谈 |
分类: CPP/C |
最近写了个多线程,在WORK线程(子线程)函数里有对窗口更新的操作,这样导致在执行子线程时会主动跳转到UI线程执行,而非正常的线程调度。
子线程的退出由UI线程控制,主要是设置一个FLAG,当为TRUE时子线程就知道我该退出了,但UI线程还需要知道子线程是否成功退出,这里我使用了WaitForSingleObject()等待子线程HANDLE,如果等待超时,则强行结束子线程,经过调试发现,无论如何等待thread
handle都是超时,最后强行结束了子线程。看代码:
FLAG = TRUE;
WaitForSingleObject(...); //
导致UI线程阻塞,切换到子线程执行,而由于子线程会对窗口更新,也因UI线程的阻塞而导致子线程阻塞,所以子线程永远无法退出,而UI线程也就一直等待,直到超时,如果等待是无限期的,则程序陷入僵局,也就发生了死锁。
因此多线程中,如果UI线程要控制子线程的进退,而子线程又会时不时主动转移到UI线程上来执行,则在UI线程中最好不要使用WaitForSingleObject(),而改为子线程退出时给UI线程发消息,通知我已经正常退出了。
当然也有别的方法来判断子线程是否成功退出。
谨防work线程和UI线程共用锁导致死锁
比如下面所示的代码:
#define UPDATE_MYLISTCTRL (WM_USER + 10)
CListCtrl m_listCtrl;
ON_MESSAGE(UPDATE_MYLISTCTRL,OnUpdateListCtrl)
void CMyDialog::OnBnClickedButton1()
{
//启动work线程,往ListCtrl里添加80000条Item
boost::thread
thread(boost::bind(&CMyDialog::WorkThreadFun,this,80000));
}
void CMyDialog::OnDestroy()
{
{
boost::mutex::scoped_lock lock(m_lock);//UI线程请求锁
//...其它操作
}
CDialog::OnDestroy();
}
void CMyDialog::WorkThreadFun(int count)
{
{
boost::mutex::scoped_lock lock(m_lock);//work线程请求锁
for(int i = 0; i < count; ++i)
{
//...其它操作
//通知往ListCtrl里加Item
::SendMessage(m_hWnd, UPDATE_MYLISTCTRL, NULL, NULL);
}
}
}
LRESULT CMyDialog::OnUpdateListCtrl(WPARAM,LPARAM)
{
//...往ListCtrl里增加新Item等更新UI操作
return 0L;
}
在上面的例子中,由于需要往ListCtrl里添加大量数据,为了防止界面等待,使用一个work线程在后台做它。如果在线程函数退出之前用户点击关闭窗口,那么UI线程将进入CMyDialog::OnDestroy()函数,当执行到boost::mutex::scoped_lock lock(m_lock)这行,UI线程试图得到锁;假定此时work线程执行到::SendMessage(m_hWnd, UPDATE_MYLISTCTRL, NULL, NULL)这行,试图给UI线程发送更新通知消息,由于SendMessage是同步调用,它必须等待消息处理函数执行完毕才能返回,也即是希望UI线程开始执行OnUpdateListCtrl函数,而此时UI线程正在OnDestroy()函数里等待work线程解锁,这里死锁就发生了。
在这种情况下,一个勉强可以的办法是用PostMessage函数代替SendMessage函数,因为PostMessage函数是立即返回的,那么work线程也就立即解锁了,UI线程OnDestroy函数得到锁继续执行;但是有另一个问题是可能OnDestroy执行完了,才开始试图执行OnUpdateListCtrl,而此时窗口已经被Destroy掉了。
因此,在UI线程和work线程里不要共用锁,以防死锁;需要锁的话也要分开,即UI线程和work线程各自用独立的锁,不要混用。
书籍推荐:多线程编程技术大全