多线程之间通信.docx
- 文档编号:4476550
- 上传时间:2022-12-01
- 格式:DOCX
- 页数:14
- 大小:23.66KB
多线程之间通信.docx
《多线程之间通信.docx》由会员分享,可在线阅读,更多相关《多线程之间通信.docx(14页珍藏版)》请在冰豆网上搜索。
多线程之间通信
当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。
用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。
现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的。
因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。
本实例针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,分别进行探讨,并利用多线程技术进行线程之间的通信,实现了数字的简单排序。
一、实现方法
1、理解线程
要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。
进程在运行时创建的资源随着进程的终止而死亡。
线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应于VisualC++中的CwinThread类对象。
单独一个执行程序运行时,缺省地包含的一个主线程,主线程以函数地址的形式出现,提供程序的启动点,如main()或WinMain()函数等。
当主线程终止时,进程也随之终止。
根据实际需要,应用程序可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。
一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。
操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。
操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。
线程被分为两种:
用户界面线程和工作线程(又称为后台线程)。
用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进程终止。
工作线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CWinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。
工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。
2、线程的管理和操作
(一)线程的启动
创建一个用户界面线程,首先要从类CwinThread产生一个派生类,同时必须使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。
第二步是根据需要重载该派生类的一些成员函数如:
ExitInstance()、InitInstance()、OnIdle()、PreTranslateMessage()等函数。
最后调用AfxBeginThread()函数的一个版本:
CWinThread*AfxBeginThread(CRuntimeClass*pThreadClass,intnPriority=THREAD_PRIORITY_NORMAL,UINTnStackSize=0,DWORDdwCreateFlags=0,LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL)启动该用户界面线程,其中第一个参数为指向定义的用户界面线程类指针变量,第二个参数为线程的优先级,第三个参数为线程所对应的堆栈大小,第四个参数为线程创建时的附加标志,缺省为正常状态,如为CREATE_SUSPENDED则线程启动后为挂起状态。
对于工作线程来说,启动一个线程,首先需要编写一个希望与应用程序的其余部分并行运行的函数如Fun1(),接着定义一个指向CwinThread对象的指针变量*pThread,调用AfxBeginThread(Fun1,param,priority)函数,返回值赋给pThread变量的同时一并启动该线程来执行上面的Fun1()函数,其中Fun1是线程要运行的函数的名字,也既是上面所说的控制函数的名字,param是准备传送给线程函数Fun1的任意32位值,priority则是定义该线程的优先级别,它是预定义的常数,读者可参考MSDN。
(二)线程的优先级
以下的CwinThread类的成员函数用于线程优先级的操作:
intGetThreadPriority();
BOOLSetThradPriority()(intnPriority);
上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;处于不同优先权层次上的线程,谁的优先权层次高,谁先运行。
至于优先级设置所需的常数,自己参考MSDN就可以了,要注意的是要想设置线程的优先级,这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。
对于线程的优先权层次的设置,CwinThread类没有提供相应的函数,但是可以通过Win32SDK函数GetPriorityClass()和SetPriorityClass()来实现。
(三)线程的悬挂和恢复
CWinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中SuspendThread()用来悬挂线程,暂停线程的执行;ResumeThread()用来恢复线程的执行。
如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。
(四)结束线程
终止线程有三种途径,线程可以在自身内部调用AfxEndThread()来终止自身的运行;可以在线程的外部调用BOOLTerminateThread(HANDLEhThread,DWORDdwExitCode)来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。
下面以第三种方法为例,给出部分代码:
;
Retrun0;
}
上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。
在控制函数中可以直接使用:
:
GetMessage()这个SDK函数进行消息分检和处理,自己实现一个消息循环。
GetMessage()函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。
(二)用事件对象实现通信
在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的对象来表示。
事件对象处于两种状态之一:
有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。
上述例子代码修改如下:
;
while(!
bend)
{
Beep(100,100);
Sleep(1000);
Intresult=:
:
WaitforSingleObject,0);
/*
主要用到的WINAPI线程控制函数,有关详细说明请查看MSDN;
线程建立函数:
HANDLECreateThread(
LPSECURITY_ATTRIBUTESlpThreadAttributes,//安全属性结构指针,可为NULL;
DWORDdwStackSize,//线程栈大小,若为0表示使用默认值;
LPTHREAD_START_ROUTINElpStartAddress,//指向线程函数的指针;
LPVOIDlpParameter,//传递给线程函数的参数,可以保存一个指针值;
DWORDdwCreationFlags,//线程建立是的初始标记,运行或挂起;
LPDWORDlpThreadId//指向接收线程号的DWORD变量;
);
对临界资源控制的多线程控制的信号函数:
HANDLECreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes,//安全属性结构指针,可为NULL;
BOOLbManualReset,//手动清除信号标记,TRUE在WaitForSingleObject后必须手动//调用RetEvent清除信号。
若为 FALSE则在WaitForSingleObject
//后,系统自动清除事件信号;
BOOLbInitialState,//初始状态,TRUE有信号,FALSE无信号;
LPCTSTRlpName//信号量的名称,字符数不可多于MAX_PATH;
//如果遇到同名的其他信号量函数就会失败,如果遇
//到同类信号同名也要注意变化;
);
HANDLECreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes,//安全属性结构指针,可为NULL
BOOLbInitialOwner,//当前建立互斥量是否占有该互斥量TRUE表示占有,
//这样其他线程就不能获得此互斥量也就无法进入由
//该互斥量控制的临界区。
FALSE表示不占有该互斥量
LPCTSTRlpName//信号量的名称,字符数不可多于MAX_PATH如果
//遇到同名的其他信号量函数就会失败,
//如果遇到同类信号同名也要注意变化;
);
//初始化临界区信号,使用前必须先初始化
VOIDInitializeCriticalSection(
LPCRITICAL_SECTIONlpCriticalSection//临界区变量指针
);
//阻塞函数
//如果等待的信号量不可用,那么线程就会挂起,直到信号可用
//线程才会被唤醒,该函数会自动修改信号,如Event,线程被唤醒之后
//Event信号会变得无信号,Mutex、Semaphore等也会变。
DWORDWaitForSingleObject(
HANDLEhHandle,//等待对象的句柄
DWORDdwMilliseconds//等待毫秒数,INFINITE表示无限等待
);
//如果要等待多个信号可以使用WaitForMutipleObject函数
*/
#include""
#include""
#include""
HANDLEevtTerminate;//事件信号,标记是否所有子线程都执行完
/*
下面使用了三种控制方法,你可以注释其中两种,使用其中一种。
注意修改时要连带修改临界区PrintResult里的相应控制语句
*/
HANDLEevtPrint;//事件信号,标记事件是否已发生
//CRITICAL_SECTIONcsPrint;//临界区
//HANDLEmtxPrint;//互斥信号,如有信号表明已经有线程进入临界区并拥有此信号
staticlongThreadCompleted=0;
/*用来标记四个子线程中已完成线程的个数,当一个子线程完成时就对ThreadCompleted进行加一操作,要使用InterlockedIncrement(long*lpAddend)和InterlockedDecrement(long*lpAddend)进行加减操作*/
//下面的结构是用于传送排序的数据给各个排序子线程
structMySafeArray
{
long*data;
intiLength;
};
//打印每一个线程的排序结果
voidPrintResult(long*Array,intiLength,constchar*HeadStr="sort");
//排序函数
unsignedlong__stdcallBubbleSort(void*theArray);//冒泡排序
unsignedlong__stdcallSelectSort(void*theArray);//选择排序
unsignedlong__stdcallHeapSort(void*theArray);//堆排序
unsignedlong__stdcallInsertSort(void*theArray);//插入排序
/*以上四个函数的声明必须适合作为一个线程函数的必要条件才可以使用CreateThread
建立一个线程。
(1)调用方法必须是__stdcall,即函数参数压栈顺序由右到左,而且由函数本身负责
栈的恢复,C和C++默认是__cdecl,所以要显式声明是__stdcall
(2)返回值必须是unsignedlong
(3)参数必须是一个32位值,如一个指针值或long类型
(4)如果函数是类成员函数,必须声明为static函数,在CreateThread时函数指针有特殊的写法。
如下(函数是类CThreadTest的成员函数中):
staticunsignedlong_stdcallMyThreadFun(void*pParam);
handleRet=CreateThread(NULL,0,&CThreadTestDlg:
:
MyThreadFun,NULL,0,&ThreadID);
之所以要声明为static是由于,该函数必须要独立于对象实例来使用,即使没有声明实例也可以使用。
*/
intQuickSort(long*Array,intiLow,intiHigh);//快速排序
intmain(intargc,char*argv[])
{
longdata[]={123,34,546,754,34,74,3,56};
intiDataLen=8;
//为了对各个子线程分别对原始数据进行排序和保存排序结果
//分别分配内存对data数组的数据进行复制
long*data1,*data2,*data3,*data4,*data5;
MySafeArrayStructData1,StructData2,StructData3,StructData4;
data1=newlong[iDataLen];
memcpy(data1,data,iDataLen<<2);//把data中的数据复制到data1中
//内存复制memcpy(目标内存指针,源内存指针,复制字节数),因为long的长度
//为4字节,所以复制的字节数为iDataLen<<2,即等于iDataLen*4
=data1;
=iDataLen;
data2=newlong[iDataLen];
memcpy(data2,data,iDataLen<<2);
=data2;
=iDataLen;
data3=newlong[iDataLen];
memcpy(data3,data,iDataLen<<2);
=data3;
=iDataLen;
data4=newlong[iDataLen];
memcpy(data4,data,iDataLen<<2);
=data4;
=iDataLen;
data5=newlong[iDataLen];
memcpy(data5,data,iDataLen<<2);
unsignedlongTID1,TID2,TID3,TID4;
//对信号量进行初始化
evtTerminate=CreateEvent(NULL,FALSE,FALSE,"Terminate");
evtPrint=CreateEvent(NULL,FALSE,TRUE,"PrintResult");
//分别建立各个子线程
CreateThread(NULL,0,&BubbleSort,&StructData1,NULL,&TID1);
CreateThread(NULL,0,&SelectSort,&StructData2,NULL,&TID2);
CreateThread(NULL,0,&HeapSort,&StructData3,NULL,&TID3);
CreateThread(NULL,0,&InsertSort,&StructData4,NULL,&TID4);
//在主线程中执行行快速排序,其他排序在子线程中执行
QuickSort(data5,0,iDataLen-1);
PrintResult(data5,iDataLen,"QuickSort");
WaitForSingleObject(evtTerminate,INFINITE);//等待所有的子线程结束
//所有的子线程结束后,主线程才可以结束
delete[]data1;
delete[]data2;
delete[]data3;
delete[]data4;
CloseHandle(evtPrint);
return0;
}
/*
冒泡排序思想(升序,降序同理,后面的算法一样都是升序):
从头到尾对数据进行两两比较进行交换,小的放前大的放后。
这样一次下来,最大的元素就会被交换的最后,然后下一次
循环就不用对最后一个元素进行比较交换了,所以呢每一次比较交换的次数都比上一次循环的次数少一,这样N次之后数据就变得升序排列了*/
unsignedlong__stdcallBubbleSort(void*theArray)
{
long*Array=((MySafeArray*)theArray)->data;
intiLength=((MySafeArray*)theArray)->iLength;
inti,j=0;
longswap;
for(i=iLength-1;i>0;i--)
{
for(j=0;j
{
if(Array[j]>Array[j+1])//前比后大,交换
{
swap=Array[j];
Array[j]=Array[j+1];
Array[j+1]=swap;
}
}
}
PrintResult(Array,iLength,"BubbleSort");//向控制台打印排序结果
InterlockedIncrement(&ThreadCompleted);//返回前使线程完成数标记加1
if(ThreadCompleted==4)SetEvent(evtTerminate);//检查是否其他线程都已执行完
//若都执行完则设置程序结束信号量
return0;
}
/*选择排序思想:
每一次都从无序的数据中找出最小的元素,然后和前面已经有序的元素序列的后一个元素进行交换,这样整个源序列就会分成两部分,前面一部分是已经排好序的有序序列,后面一部分是无序的,用于选出最小的元素。
循环N次之后,前面的有序序列加长到跟源序列一样长,后面的无序部分长度变为0,排序就完成了。
*/
unsignedlong__stdcallSelectSort(void*theArray)
{
long*Array=((MySafeArray*)theArray)->data;
intiLength=((MySafeArray*)theArray)->iLength;
longlMin,lSwap;
inti,j,iMinPos;
for(i=0;i { lMin=Array[i]; iMinPos=i; for(j=i+1;j<=iLength-1;j++)//从无序的元素中找出最小的元素 { if(Array[j] { iMinPos=j; lMin=Array[j]; } } //把选出的元素交换拼接到有序序列的最后 lSwap=Array[i]; Array[i]=Array[iMinPos]; Array[iMinPos]=lSwap; } PrintResult(Array,iLength,"SelectSort");//向控制台打印排序结果 InterlockedIncrement(&ThreadCompleted);//返回前使线程完成数标记加1 if(ThreadCompleted==4)SetEvent(evtTerminate);//检查是否其他线程都已执行完 //若都执行完则设置程序结束信号量 return0; } /*堆排序思想: 堆: 数据元素从1到N排列成一棵二叉树,而且这棵树的每一个子树的根都是该树中的元素的最小或最大的元素这样如果一个无序数据集合是一个堆那么,根元素就是最小或最大的元素堆排序就是不断对剩下的数据建堆,把最小或最大的元素析透出来。 下面的算法,就是从最后一个元素开始,依据一个节点比父节点数值大的原则对所有元素进行调整,这样调整一次就形成一个堆,第一个元素就是最小的元素。 然后再对剩下的无序数据再进行建堆,注意这时后面的无序数据元素的序数都要改变,如第一次建堆后,第二个元素就会变成堆的第一个元素。 */ unsignedlong__stdcallHeapSort(void*theArray) { long*Array=((MySafeArray*)theArray)->data; intiLength=((MySafeArray*)theArray)->iLength; inti,j,p; longswap; for(i=0;i { for(j=iLength-1;j>i;j--)//从最后倒数上去比较字节点和父节点 { p=(j-i-1)/2+i;//计算父节点数组下标 //注意到树节点序数跟数组下标不是等同的,因为建堆的元素个数逐个递减 if(Array[j] { swap=Array[j]; Array[j]=Array[p]; Array[p]=swap; } } } PrintResult(Array,iLength,"Hea
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 多线程 之间 通信