重工电子论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 4887|回复: 1
打印 上一主题 下一主题

[VC] 【MFC多线程】多线程锁死的一个问题(内有我的分析)

[复制链接]

287

主题

668

帖子

5636

积分

学生管理组

Rank: 8Rank: 8

积分
5636
跳转到指定楼层
楼主
发表于 2015-6-16 13:05:55 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 李维强-15级 于 2015-6-16 13:11 编辑

以下代码是模拟一个最近我实际发生的情况,我的程序是,开启一个线程SendThread去发送数据,发送完了线程就自动退出,但是发送数据可能要一段时间才能发完,比如说要10秒才发完,但是用户在操作的时候,很有可能要求在在之前那个数据发送后3秒的时候就要发另外一组数据,更或者刚刚执行完启动一个线程发数据的函数AfxBeginThread(SendThread,this),用户又要执行这个函数去发送另外一组新数据,这样一来就需要发信号给上一个线程让其退出,然后主线程里面判断这个线程是否退出了

下面是我的代码, 然后再引出问题:
这是一个基于对话框的程序,
首先在头文件里面有几个变量的声明:
[C++] syntaxhighlighter_viewsource syntaxhighlighter_copycode
	BOOL runflag;		//这个为否正在发送数据的标志
	int uiButton;		//这个是表明哪一个函数启动的发送线程 的标志
	
	HANDLE m_CANEvent;		//事件
	CWinThread *pCANThreads;	//线程指针
	


下面是CPP里面的代码
[C++] syntaxhighlighter_viewsource syntaxhighlighter_copycode
BOOL CExample2Dlg::OnInitDialog()
{
	CDialog::OnInitDialog();
…
…
	m_CANEvent=CreateEvent(NULL,FALSE,FALSE,NULL);	//声明一个事件
	
	return TRUE;  // return TRUE  unless 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里面加一句延时

[C++] syntaxhighlighter_viewsource syntaxhighlighter_copycode
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,就有了现在这个帖子

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

287

主题

668

帖子

5636

积分

学生管理组

Rank: 8Rank: 8

积分
5636
沙发
 楼主| 发表于 2015-6-16 13:14:12 | 只看该作者
解决方法:

子线程不要直接对UI进行 SetWindowText 等之类的界面操作, 而是把一些操作弄成自定义消息, 子线程向主线程发送消息,主线程收到各种消息过后再做相应的操作,这样才能解决问题。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|cqutlab ( 渝ICP备15004556号

GMT+8, 2024-4-30 02:43 , Processed in 0.167247 second(s), 27 queries .

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表