【MFC多线程】多线程锁死的一个问题(内有我的分析)
本帖最后由 李维强-15级 于 2015-6-16 13:11 编辑以下代码是模拟一个最近我实际发生的情况,我的程序是,开启一个线程SendThread去发送数据,发送完了线程就自动退出,但是发送数据可能要一段时间才能发完,比如说要10秒才发完,但是用户在操作的时候,很有可能要求在在之前那个数据发送后3秒的时候就要发另外一组数据,更或者刚刚执行完启动一个线程发数据的函数AfxBeginThread(SendThread,this),用户又要执行这个函数去发送另外一组新数据,这样一来就需要发信号给上一个线程让其退出,然后主线程里面判断这个线程是否退出了
下面是我的代码, 然后再引出问题:
这是一个基于对话框的程序,
首先在头文件里面有几个变量的声明:
BOOL runflag; //这个为否正在发送数据的标志
int uiButton; //这个是表明哪一个函数启动的发送线程 的标志
HANDLE m_CANEvent; //事件
CWinThread *pCANThreads; //线程指针
下面是CPP里面的代码
BOOL CExample2Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
…
…
m_CANEvent=CreateEvent(NULL,FALSE,FALSE,NULL); //声明一个事件
return TRUE;// return TRUEunless you set the focus to a control
}
void CExample2Dlg::OnButton1() //这是一个按钮响应函数,用按钮开启线程的函数
{
// TODO: Add your control notification handler code here3
ResetEvent(m_CANEvent); //设置无信号,让线程进入等待超时的工作
uiButton=1; // 1,标记这是按钮响应的函数启动的线程
pCANThreads=AfxBeginThread(MyThread1,this); //启动线程
runflag=1; //设置正在发送标识
MyStartThread(); //这里就是模拟用户在启动了上一个线程过后,紧接着再次启动一个线程
}
void CExample2Dlg::MyStartThread() //另外一个开启线程的函数
{
if(runflag==1) //如果线程正在运行
{
SetEvent(m_CANEvent); //发出让线程结束
if( WAIT_OBJECT_0 == WaitForSingleObject(pCANThreads->m_hThread,INFINITE) )//让其等待建立的线程退出过后才返回
{
}
} //等到之前的线程返回了 ,才可以执行下面代码,再次新开线程
ResetEvent(m_CANEvent); //设置无信号,让线程进入等待超时的工作
uiButton =2; //标记这是自定义函数启动的线程
runflag=1; //设置正在发送标识
pCANThreads=AfxBeginThread(MyThread1,this);
}
UINT CExample2Dlg::MyThread1(void *param)
{
CExample2Dlg *Dlg=(CExample2Dlg *)param;
int i;
CString tmp;
DWORD dw;
//在进入下面循环前,在我那个真实程序前,还有不少Dlg->XXXX的工作,在线程里面操作主界面的一//些显示,读取数据等工作
for(i=0;i<100000;i++) //这里是我人为模拟 要让它循环这么多次,意为模拟发送这么多次
{
dw = WaitForSingleObject(Dlg->m_CANEvent,10); //让其延迟10ms就输出一个数
if( dw == WAIT_OBJECT_0 ) //收到结束线程信号
{
Dlg->runflag=0; //线程运行标志复位
return 0;
}
if( dw == WAIT_TIMEOUT) //延迟到了,就执行下面的显示数值
{
if(Dlg->uiButton == 1)
{
tmp.Format(" %d ",i);
Dlg->m_count.SetWindowText(tmp); //这个是让其在界面上面显示当前i的数值
} // Dlg->m_count是主界面上面的一个static控件
if(Dlg->uiButton ==2 )
{
tmp.Format(" %d ",i);
Dlg->m_count2.SetWindowText(tmp); //这个是让其在界面上面显示当前i的数值
} // Dlg->m_count是主界面上面的一个static控件
}
}
Dlg->runflag=0; //线程运行标志复位
return 0;
}
void CExample2Dlg::OnButton2()
{
// TODO: Add your control notification handler code here
SetEvent(m_CANEvent);
}
用上面的程序,在点击那个button1过后程序可以正常跑,正常开启线程,然后再进入MyStartThread() 发送线程结束信号,再等待线程结束,再开启线程。然后点击Button2也可以正常让线程停下
但是如果我模拟下真实情况,也就是在Button1里面加一句延时
void CExample2Dlg::OnButton1() //这是一个按钮响应函数,用按钮开启线程的函数
{
// TODO: Add your control notification handler code here3
ResetEvent(m_CANEvent); //设置无信号,让线程进入等待超时的工作
uiButton=1; // 1,标记这是按钮响应的函数启动的线程
pCANThreads=AfxBeginThread(MyThread1,this); //启动线程
runflag=1; //设置正在发送标识
Sleep(1000); //加个延时 也许用户等了一点时间再去执行的下面那个函数
MyStartThread(); //这里就是模拟用户在启动了上一个线程过后,紧接着再次启动一个线程
}
加了那个延时过后,程序就锁死,新建线程也不跑了,主线程也死了。。
我经过长时间分析,发现Dlg->m_count.SetWindowText默认的消息机制为SendMessage与主进程中MFC主窗体交互,SendMessage为同步方法,会等待主进程响应后才返回,否则一直等待。而且我真实程序里面,还有大量的在新建线程里面调用Dlg->XXX等空间去读取列表空间啊,编辑框啊,更新列表空间啊,编辑框啊等操作。所以说,新线程只要执行到Dlg->XXX的时候就SendMessage,而这个时候主线程可能正在调用WaitForSingleObj,主进程却在等待子进程结束,而子进程中SendMessage有在等待主进程窗体消息循环的响应。所以....就一直处于等待状态了。 这就是为什么我加了Sleep(1000);过后,就说不清楚子线程跑到哪里了,大部分概率都会跑到Dlg->m_count.SetWindowText这些地方。
而之前我不加Sleep(1000);为什么会把线程跑起来,我跟踪了代码过后 发现进入了MyStartThread();了,SetEvent(m_CANEvent);过后 子线程才开始跑起来,所以 子线程可以顺利退出。
那么现在的问题就是:
怎么让主线程不锁死,而接收消息,,但是也不让主线程向下执行代码。
用户可能在上一次没有发完的情况下就完再次发送,用户在主界面改一改信息,他又要发,
实际情况就是这里的主线程也是我那个程序的子线程,我真实的程序是开始运行后,就开启线程1去执行预先设定好的一些条目,其中设定好的一个条目就是类似这个帖子里面的发数据,所以当线程1执行到这个条目A的时候,就要开启线程2去发数据,然后线程1就会执行下一个条目,这个时候线程2还在发数据,但是线程1可能又执行到条目A,只是要求发送的数据不一样,但是同样要求开启线程2去发送,这个时候,就需要让先前的线程2结束,再去开起线程2,就有了现在这个帖子
解决方法:
子线程不要直接对UI进行 SetWindowText 等之类的界面操作, 而是把一些操作弄成自定义消息, 子线程向主线程发送消息,主线程收到各种消息过后再做相应的操作,这样才能解决问题。
页:
[1]