AFX图形界面和多线程

4 篇文章 0 订阅
3 篇文章 0 订阅

使用this指针从工作线程向界面线程发送消息(转自:http://blog.csdn.net/gukesdo/article/details/6887323)

前一段时间使用MFC写程序的时候,为了实现从一个窗口向另一个窗口发送消息,使用过下面两种方法

/*方法一:通过用SDK的标准API来查找其他对话框窗口返回句柄,并且发送信息*/
HWND hWnd;
//通过SDK的FindWindow函数得到目标窗口的句柄,TriTest为目标串口的Caption的值
if(!(hWnd = ::FindWindow(NULL,"TriTest")))
  AfxMessageBox("Error!");
//通过SDKSendMessage向目标窗口发送EDGE_MESSAGE消息,此消息在staafx.h中已经定义
::SendMessage(hWnd,EDGE_MESSAGE,0,0);

/*方法二:通过运用MFC自身封装好的CWnd中的函数(跟标准API有一些不同)实现,不同之处在于省略了标准API第一个参数*/
CWnd* pWnd = CWnd::FindWindow(NULL,"TriTest");
if(pWnd == NULL)
  AfxMessageBox("Error!");
pWnd->SendMessage(EDGE_MESSAGE,0,0);
但是这次使用MFC在后台工作线程 使用这两种方法发送消息的时候,有时候发送成功,有时候发送失败,最终没有找到原因,师兄说可能是窗口切换的时候出现了问题,最后提供了一种很灵巧的解决方案:
首先定义了消息MY_MSG,消息响应函数MT_Test,并关联了消息响应函数,当单击按钮时:
#define  MY_MSG WM_USER+1

afx_msg LRESULT MT_Test(WPARAM wParam,LPARAM lParam);
ON_MESSAGE(MY_MSG, &CMT_MFCDlg::MT_Test)

void CMT_MFCDlg::OnBnClickedButton1()
{
 AfxBeginThread(Caculate,this);//在这里把这个对象(例程/实例)的指针传给线程函数
}

//Caculate线程工作函数如下:
UINT Caculate(LPVOID pParam)
{
 AfxMessageBox("Come here");
 CMT_MFCDlg* c = (CMT_MFCDlg *)pParam; //通过传来的对象指针直接向这个对象发消息,肯定可以收到
 c->SendMessage(MY_MSG,i,0);
 return 0;
}
//消息响应函数如下:
LRESULT CMT_MFCDlg::MT_Test(WPARAM wParam,LPARAM lParam)
{
 AfxMessageBox("Receive MY_MSG");      //如果收到消息弹出对话框提示
  return 0;
}
经过这样的控制,每次都可以发送成功消息。
在这里,师兄巧妙运用了this指针和函数传递指针类型参数--->LPVOID--->强制转换回来,之后发送消息

关于this我理解如下:关于this指针的一个经典回答:
当你进入一个房子后,你可以看见桌子、椅子、地板等,但是房子你是看不到全貌了。对于一个类的实例来说,你可以看到它的成员函数、成员变量,但是实例本身呢?this是一个指针,它时时刻刻指向你这个实例本身。

C++中的每个对象(实例)都有一个隐藏的this指针,在MFC中可以再线程处理函数里利用这个指针,向这个实例发送消息进行处理。

-----------------------------------------------------------------分割线------------------------------------------------------------------

访问方法总结:

在界面线程中访问控件:
1.通过控件ID访问:CView的GetDlgItem(...)、GetDlgItemInt(...)、GetDlgItemText(...);SetDlgItem(...)、SetDlgItemInt(...)、SetDlgItemText(...)。这些方法一般用于临时性访问,例如用于对话框中。
2.通过关联访问:使用类向导在CView中添加变量成员。

句柄和指针的区别:
句柄是由系统维护的,句柄经过系统间接地访问指针。句柄指向的目标称为“资源”。句柄只能调用系统提供的服务,所以使用句柄比较安全。句柄还提供一些指针不直接具有的东西,例如句柄知道所指的内存有多大。但是资源的作用域是线程,所以在一个线程中通过句柄是找不到另一个线程中的资源的。可能只有全局函数例如AfxGetMainWnd()等例外。

MFC的窗口类和其消息函数都是线程安全的。最佳做法:把界面线程的对象指针传入工作线程,句柄在消息函数中作为参数。很可能二级成员是不行的,那么就使用一级成员,同时零级成员(即直接传入控件指针)也是可以的。

线程的同步:

MFC:CCriticalSection, CMutex, CEvent, CSemaphore。比Windows API多了一个CSemaphore——信号量。

CMutex, CCriticalSection用于对资源的互斥访问,CMutex可以跨进程使用, CCriticalSection只可以在进程内部使用. 相对的创建CMutex需要更多的资源. 只用于进程内部时使用CCriticalSection可以获得更好的效率. 执行多次(例如1000000)的Lock()和Unlock()可以看到明显的效率差别。
CSemaphore用于限制特定个数(信号量)的线程对资源的访问。
CEvent实现事件, 用于线程同步。

这四个MFC类都有成员函数进行加锁和解锁。有的时候,可以使用CSingleLock、CMultiLock两个类对这四个MFC类的对象进行更健壮复杂的加锁和解锁。例如,在锁定临界区期间可能发生异常导致不能解锁,使用后面两个类就不会解不了锁。这六个MFC类的使用可以查阅《MFC Windows程序设计(第2版)(修订版)Jeff Prosise》第17章。

-----------------------------------------------------------------分割线------------------------------------------------------------------
对几个同步对象的性能测试(转自http://bbs.csdn.net/topics/390630298)
// FastMutex.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

/* Define 4 MUTual EXclusion mechanisms:
 *   Mutex: a plain mutex
 *   FastMutex: a mutex
 *   SpinMutex: a spinning mutex
 *   NTCritical_Section: NT's critical section
 */

//----------------------------------------------------------------------------
// A plain NT mutex
class Mutex {
        HANDLE mux;
public:
        Mutex()
          : mux(CreateMutex(NULL,FALSE,NULL))
          {}
        ~Mutex()
          { CloseHandle(mux); }
        void Request()
          { WaitForSingleObject(mux,INFINITE); }
        void Release()
          { ReleaseMutex(mux); }
};

//----------------------------------------------------------------------------
// A fast, but friendly, mutex
class FastMutex {
        LONG toggle;
        LONG waiters;
        HANDLE try_again;
public:
        FastMutex()
          : toggle(0),
            waiters(0),
            try_again(CreateEvent(NULL,TRUE,FALSE,NULL))
          {}
        ~FastMutex()
          { CloseHandle(try_again); }
        void Request();
        void Release();
};

void FastMutex::Request() {
        InterlockedIncrement(&waiters);
        for(;;) {
                if(InterlockedExchange(&toggle,1)==0) {
                        InterlockedDecrement(&waiters);
                        return;
                }
                WaitForSingleObject(try_again,INFINITE);
                ResetEvent(try_again);
        }
}

void FastMutex::Release() {
        toggle=0;
        if(waiters!=0)
                SetEvent(try_again);
}

//----------------------------------------------------------------------------
// A very fast, but friendly, spinning mutex
// This is not a true spinlock since it will block the thread if spinning seems futile

class SpinMutex {
        enum {
          spin_retries=100 //tailor this value to suit you application/compiler
        };
        LONG toggle;
        LONG waiters;
        HANDLE try_again;
public:
        SpinMutex()
          : toggle(0),
            waiters(0),
            try_again(CreateEvent(NULL,TRUE,FALSE,NULL))
          {}
        ~SpinMutex()
          { CloseHandle(try_again); }
        void Request();
        void Release();
};

void SpinMutex::Request() {
        for(unsigned spin=0; spin<spin_retries; spin++) {
                if(InterlockedExchange(&toggle,1)==0)
                        return;
        }
        InterlockedIncrement(&waiters);
        for(;;) {
                if(InterlockedExchange(&toggle,1)==0) {
                        InterlockedDecrement(&waiters);
                        return;
                }
                WaitForSingleObject(try_again,INFINITE);
                ResetEvent(try_again);
        }
}

void SpinMutex::Release() {
        toggle=0;
        if(waiters!=0)
                SetEvent(try_again);
}

//----------------------------------------------------------------------------
//NT's critical sections
class NTCritialSection {
        CRITICAL_SECTION cs;
public:
        NTCritialSection()
          { InitializeCriticalSection(&cs); }
        ~NTCritialSection()
          { DeleteCriticalSection(&cs); }
        void Request() {
                EnterCriticalSection(&cs);
        }
        void Release() {
                LeaveCriticalSection(&cs);
        }
};

//   1: 9.359 9.093 9.125 9.250
//  10: 9.460 9.547 9.266 9.390
// 100: 9.281 9.250 9.500 9.359
//1000: 9.344 9.438 9.406 9.390

#include <time.h>
#include <stdio.h>
//Uncomment one of these to test them
//Mutex mux;            //running time: 42.25/42.20/42.20
//FastMutex mux;        //running time: 3.73/4.48/4.45
//SpinMutex mux;        //running time: 0.94/0.94/0.94
NTCritialSection mux; //running time: 42.40/40.80/42.03

static inline loop() {
        //spend some time ...
        for(unsigned i=0; i<1000000; i++) {
                mux.Request();
                mux.Release();
        }
}

DWORD WINAPI OtherThread(LPVOID) {
        loop();
        ExitThread(0);
        return 0;
}

int main(int,char**)
{
        clock_t start=clock();

        HANDLE hThread;
        DWORD idThread;
        //create secondary thread
        hThread = CreateThread(NULL, 16384, OtherThread, NULL, 0, &idThread);
        //compete with the secondary thread
        loop();
        //wait for secondary thread to finish
        WaitForSingleObject(hThread,INFINITE);
        clock_t end=clock();
        CloseHandle(hThread);
        
        printf("Running time: %10.3f seconds\n", ((double)(end-start))/CLK_TCK );
        return 0;
}

测试结果:互斥对象的性能低一个数量级,其它最多是两倍差距。
测试结论:微软都给安排好了。按照语义,怎么用方便怎么用。
-----------------------------------------------------------------分割线------------------------------------------------------------------
线程的暂停、继续、终止:通过上述的几个同步对象作为判断条件去调用线程的下列函数:
CWinThread.SuspendThread();
CWinThread.ResumeThread();
只要CWinThread(或其派生类)对象的线程函数正常返回,就会在返回后立即自动调用AfxEndThread()函数,释放资源和CWinThread对象(delete this)。
需要反复执行的后台任务,既可以创建新工作线程,也可以在线程函数中采用循环结构实现。前者可读性高,单纯由界面操作引发的话,开支忽略不计。
* 异常处理:应该按照微软的做法,做上总的异常处理,例如一个线程做上一个总的异常捕捉。这是对异常的合适的处理方式。Java那种方式会围着异常转,被异常活埋。
* GUI部分是最麻烦最耗时的部分。
-----------------------------------------------------------------分割线------------------------------------------------------------------

视os的进程调度算法而定,一般情况不保证“a线程一定运行在a核,b线程一定运行在b核”。默认的情况下视调度算法而定,但是可以手动设置CPU的亲和性,指定核心来运行不同的线程。如果线程关联较多的话,分两个核来跑不一定能达到提高效率的目的。

什么时候该使用多线程呢?这要分四种情况讨论:
a.多核CPU——计算密集型任务。此时要尽量使用多线程,可以提高任务执行效率,例如加密解密,数据压缩解压缩(视频、音频、普通数据),否则只能使一个核心满载,而其他核心闲置。
b.单核CPU——计算密集型任务。此时的任务已经把CPU资源100%消耗了,就没必要也不可能使用多线程来提高计算效率了;
相反,如果要做人机交互,最好还是要用多线程,避免用户没法对计算机进行操作。
c.单核CPU——IO密集型任务,使用多线程还是为了人机交互方便,
d.多核CPU——IO密集型任务,这就更不用说了,跟单核时候原因一样。

红色代表针对计算密集型的讨论(多核加快计算速度);
绿色代表针对IO密集型的讨论(就是避免因为IO而程序卡死);

4.程序员需要掌握的技巧/技术

(1)减少串行化的代码用以提高效率。这是废话。
(2)单一的共享数据分布化:把一个数据复制很多份,让不同线程可以同时访问。
(3)负载均衡,分为静态的和动态的两种。具体的参见有关文献。浅谈多核CPU、多线程与并行计算

如果线程关联较多的话,分两个核来跑不一定能达到提高效率的目的。
--------------------------------------------------------------分割线---------------------------------------------------------------------

双缓存解决闪烁及对话框背景覆盖控件问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值