李维强-15级 发表于 2015-6-16 13:05:55

【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,就有了现在这个帖子

李维强-15级 发表于 2015-6-16 13:14:12

解决方法:

子线程不要直接对UI进行 SetWindowText 等之类的界面操作, 而是把一些操作弄成自定义消息, 子线程向主线程发送消息,主线程收到各种消息过后再做相应的操作,这样才能解决问题。
页: [1]
查看完整版本: 【MFC多线程】多线程锁死的一个问题(内有我的分析)