您的位置:首页 > 其它

AFX图形界面和多线程

2016-04-27 20:20 429 查看
使用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、多线程与并行计算
如果线程关联较多的话,分两个核来跑不一定能达到提高效率的目的。

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


双缓存解决闪烁及对话框背景覆盖控件问题
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多线程 AFX MFC VC6