VC 多线程编程

VC多线程中需要熟悉的知识点是:

1. 进程和线程的概念;

2. 如何通过WinAPI,MFC两种方式创建线程;

3. 线程传参数,包括简单参数和结构体复杂参数;

4. MFC线程分为用户界面线程和工作者线程,两种之间的区别和创建方式是什么;

5. 线程间的通信分为几种,具体怎么实现;

6. 线程之间的同步方式有几种,具体怎么实现;

1. 进程和线程的概念;

程序是计算机指令的集合,它以文件的形式存储在硬盘上,如记事本程序:notepad.exe,计算器程序:calc.exe。进程被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程终止,资源释放。

一个程序可以对应多个进程,比如用打开两个计算器程序,就是两个进程。

线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程。

进程是线程的执行环境,线程是进程内部的一个执行单元。一个进程至少包括一个线程,称为主线程(系统自动创建);用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。如果只有一个主线程,且主线程阻塞(Sleep(1000);),那么程序就会一动不动。多个线程的话Sleep自动释放本线程时间片,其他线程可获得执行权。一个进程内的虚拟地址空间、全局变量和系统资源由多个线程共享,所以线程间通信比较简单,进程间通信较为复杂。

2. 如何通过WinAPI,MFC两种方式创建线程;     

1>. WinAPI创建方式:CreateThread:创建线程;SetThreadPriotity:设置线程优先级; SuspendThread:挂起线程;ResumeThread:结束挂起,重新执行;ExitThread:终止线程,在线程函数中执行;TerminateThread:强行终止线程(不安全,不建议使用);PostThreadMessage(向线程发送消息)。

DWORD ThreadID;

HANDLE hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFunc,NULL,0,&ThreadID);

volatile BOOL m_bRun;

void ThreadFunc(){

CTime time;CString strTime;m_bRun=TRUE;

while(m_bRun){

time=CTime::GetCurrentTime();

strTime=time.Format("%H:%M:%S");

::SetDlgItemText(AfxGetMainWnd()->m_hWnd,IDC_TIME,strTime);

Sleep(1000);

}}

2> MFC中创建线程:AfxBeginThread。

3.线程传参数,包括简单参数和结构体复杂参数;

Void ThreadFunc(int integer){

int i;

for(i=0;i<integer;i++){}

}int integer=m_nCount;

hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFunc,(VOID*)integer,0,&ThreadID);

传送一个结构体给一个线程函数也是可能的,可以通过传送一个指向结构体的指针参数来完成。先定义一个结构体:

typedef struct{

int firstArgu,

long secondArgu,

}myType,*pMyType;

创建线程时CreateThread(NULL,0,threadFunc,pMyType,…);

在threadFunc函数内部,可以使用“强制转换”:

int intValue=((pMyType)lpvoid)->firstArgu;

long longValue=((pMyType)lpvoid)->seconddArgu;

……

4. MFC线程分为用户界面线程和工作者线程,两种之间的区别和创建方式是什么;

  MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。

  工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。

两者均用AfxBeginThread函数创建,但是参数略有不同。

用户界面线程经常重载InitInstance函数和ExitInstance,工作者线程一般不使用InitInstance和ExitInstance。

UINT ThreadFunc(LPVOID lpParam);

CWinThread* pThread;

pThread=AfxBeginThread(ThreadFunc,&Info); //struct

创建用户界面线程的步骤:

使用ClassWizard创建类CWinThread的派生类. 重载函数InitInstance()和ExitInstance()。

在线程中创建窗口:

1,由CWinThread派生出一个子类;

2,在子类的InitInstance中设置线程的主窗口;

3,调用AfxBeginThread函数创建用户界面线程;

class CSubThread : public CWinThread{bool Released;    false}

InitInstance: CSubDlg dlg; m_pMainWnd=&dlg; dlg.DoModal();

m_pThread = (CSubThread*)AfxBeginThread(

RUNTIME_CLASS(CSubThread),0,0,0,NULL);

if(m_pThread!=NULL){ if(!m_pThread->Released){ delete m_pThread; } }

 

5. 线程间的通信分为几种,具体怎么实现;

   一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程

和次要线程间要进行通信。

1.使用全局变量进行通信  建议使用volatile修饰全局变量.

2. 使用自定义消息  注意接收消息的线程必须已经建立了消息循环。

m_pCalculateThread->PostThreadMessage(WM_CALCULATE,nAddend,NULL);

6. 线程之间的同步方式有几种,具体怎么实现;

线程同步方式主要有四种:

临界区(CCriticalSection)

事件(CEvent)

互斥量(CMutex)

信号量(CSemaphore)

1>.临界区  临界区使用最简单,三步即可:

定义CCriticalSection类的一个全局对象(以使各个线程均能访问),如CCriticalSection critical_section

在访问需要保护的资源或代码之前,调用CCriticalSection类的成员Lock()获得临界区对象: critical_section.Lock();

在线程中调用该函数来使线程获得它所请求的临界区。如果此时没有其它线程占有临界区对象,则调用Lock()的线程获得临界区;否则,线程

将被挂起,并放入到一个系统队列中等待,直到当前拥有临界区的线程释放了临界区时为止。

访问临界区完毕后,使用CCriticalSection的成员函数Unlock()来释放临界区:critical_section.Unlock();

CCriticalSection critical_section;//1

critical_section.Lock();//2

critical_section.Unlock();//3

2>. CEvent类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。

每一个CEvent 对象可以有两种状态:有信号状态和无信号状态。线程监视位于其中的CEvent类对象的状态,并在相应的时候采取相应的操作。(有信号就是有资源可用)

在MFC中,CEvent 类对象有两种类型:人工事件和自动事件。一个自动CEvent对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得信号后,

释放可利用线程,但直到调用成员函数ResetEvent ()才将其设置为无信号状态。在创建CEvent类的对象时,默认创建的是自动事件。 

人工事件才有:ResetEvent();我们一般通过调用WaitForSingleObject函数来监视事件状态。

WaitForSingleObject(eventWriteD.m_hObject,INFINITE);处等待,直到事件eventWriteD为有信号该线程才往下执行。

3>、使用CMutex 类

  互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用

。当然,互斥对象也可以用于同一进程的各个线程间,但是在这种情况下,使用临界区会更节省系统资源,更有效率。

4>使用CSemaphore 类

当需要一个计数器来限制可以使用某个线程的数目时,可以使用“信号量”对象。CSemaphore类的对象保存了对当前访问某一指定资源的线程的计数值,

该计数值是当前还可以使用该资源的线程的数目. 线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源数加1。

附录:

1. AfxBeginThreadCreateThread具体区别

具体说来,CreateThread这个 函数是windows提供给用户的 API函数,是SDK的标准形式,在使用的过程中要考虑到进程的同步与互斥的关系,进程间的同步互斥等一系列会导致操作系统死锁的因素,用起来比较繁琐一些,初学的人在用到的时候可能会产生不可预料的错误,建议多使用AfxBeginThread,是编译器对原来的CreateThread函数的封装,用与MFC编程(当然,只要修改了项目属性,consolewin32项目都能调用)而_beginthreadC的运行库函数。

在使用AfxBeginThread时,线程函数的定义为:UINT   _yourThreadFun(LPVOID   pParam)参数必须如此在使用CreateThread时,线程的函数定义为: DWORD WINAPI _yourThreadFun(LPVOID pParameter)

1. WaitForSingleObject

WaitForSingleObject函数原型为:DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);

hHandle为要监视的对象(一般为同步对象,也可以是线程)的句柄;

dwMillisecondshHandle对象所设置的超时值,单位为毫秒;有信号就是可以进行下一步,没有信号就是一直等待,等待信号才进行下一步。WaitForSingleObject()就是等待信号的函数。

  当在某一线程中调用该函数时,线程暂时挂起,系统监视hHandle所指向的对象的状态。如果在挂起的dwMilliseconds毫秒内,线程所等待

的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函

数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,

直到hHandle所指向的对象变为有信号状态时为止。具体可参看MSDN

2. 进程间通信方式: 剪切板,匿名管道,命名管道,邮槽。

进程就像一本活页笔记夹,你可以在其中的活页上写东西,也可以擦掉内容或甚至整页撕掉,活页笔记夹只是持有那些东西而已。同理,进程本身并不能够执行,它只是提供一个安置内存和线程的地方(译注)。进程就是一大堆对象的拥有权的集合。也就是说,进程拥有对象。进程可以拥有内存(更精确地说是拥有memory context),可以拥有file handles,可以拥有线程,可以拥有一大串DLL 模块(被载入这一进程的地址空间中)内存:Code,Data,Stack.:线程价廉。线程启动比较快,退出比较快,对系统资源的

冲击也比较小。而且,线程彼此分享了大部分核心对象(如file handles)的拥有权。

如果有许多个线程,就可以令某些线程等待系统资源,如网络和硬盘。操作系统可以很有效率地处理这些请求,因为它可以在任何时刻知道谁正在等待什么,并决定怎样调整最好。一部双CPU 系统可以同时执行同一进程(或不同进程)的两个线程

主线程有两个特点。第一,它必须负责GUIGraphic User Interface)程序中的主消息循环。第二,这一线程的结束(不论是因为返回或因为调用了ExitThread( ))会使得程序中的所有线程都被强迫结束,程序也因此而结束。其他线程没有机会做清理工作。


参考:韩耀旭的文章《多线程编程》http://www.vckbase.com/document/viewdoc/?id=1704


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值