Windows多线程多任务设计初步(转)

Windows多线程多任务设计初步

[前言:]当前流行的Windows操作系统,它能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的应用软件无一不是多线程多任务处理,单线城的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本文针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,对它们分别进行探讨。 http://www.mscto.com

  一、理解线程

  要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应VisualC
中的CwinThread类的对象。单独一个执行程序运行时,缺省的运行包含的一个主线程,主线程以函数地址的形式,如main或WinMain函数,提供程序的启动点,当主线程终止时,进程也随之终止,但根据需要,应用程序又可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。

  一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。
http://www.mscto.com

  线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进城终止。工作者线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CwinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。
二、线程的管理和操作

软件开发网

  1.线程的启动

  创建一个用户界面线程,首先要从类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。

2.线程的优先级

  以下的CwinThread类的成员函数用于线程优先级的操作:

intGetThreadPriority();
BOOLSetThradPriority()(intnPriority); http://www.mscto.com

上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;处于不同优先权层次上的线程,谁的优先权层次高,谁先运行。至于优先级设置所需的常数,自己参考MSDN就可以了,要注意的是要想设置线程的优先级,这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置,CwinThread类没有提供相应的函数,但是可以通过Win32SDK函数GetPriorityClass()和SetPriorityClass()来实现。

  3.线程的悬挂、恢复

  CwinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中SuspendThread()用来悬挂线程,暂停线程的执行;ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。

  4.结束线程 软件开发网

  终止线程有三种途径,线程可以在自身内部调用AfxEndThread()来终止自身的运行;可以在线程的外部调用BOOLTerminateThread(HANDLEhThread,DWORDdwExitCode)来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。下面以第三种方法为例,给出部分代码:

////////////////////////////////////////////////////////////////
//////CtestViewmessagehandlers
/////SettoTruetoendthread
Boolbend=FALSE;//定义的全局变量,用于控制线程的运行
//TheThreadFunction
UINTThreadFunction(LPVOIDpParam)//线程函数
{
while(!bend)
{Beep(100,100);
Sleep(1000);
}
return0;
}
CwinThread*pThread;
HWNDhWnd;
/////////////////////////////////////////////////////////////
VoidCtestView::OninitialUpdate()
{
hWnd=GetSafeHwnd();
pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程
pThread->m_bAutoDelete=FALSE;//线程为手动删除
Cview::OnInitialUpdate();
}
////////////////////////////////////////////////////////////////
VoidCtestView::OnDestroy()
{bend=TRUE;//改变变量,线程结束
WaitForSingleObject(pThread->m_hThread,INFINITE);//等待线程结束
deletepThread;//删除线程
Cview::OnDestroy();
}
三、线程之间的通信

  通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。

  1.利用用户定义的消息通信

http://www.mscto.com

  在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息,如下所示:#defineWM_USERMSGWMUSER
100;在需要的时候,在一个线程中调用

http://www.mscto.com

::PostMessage((HWND)param,WM_USERMSG,0,0)

CwinThread::PostThradMessage()

软件开发网

来向另外一个线程发送这个消息,上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改,修改后的结果是在线程结束时显示一个对话框,提示线程结束:

UINTThreadFunction(LPVOIDpParam)
{
while(!bend)
{
Beep(100,100);
Sleep(1000);
}
::PostMessage(hWnd,WM_USERMSG,0,0);
return0;
}
////////WM_USERMSG消息的响应函数为OnThreadended(WPARAMwParam,LPARAMlParam)
LONGCTestView::OnThreadended(WPARAMwParam,LPARAMlParam)
{
AfxMessageBox("Threadended.");
Retrun0;
} 软件开发网

上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。在控制函数中可以直接使用::GetMessage()这个SDK函数进行消息分检和处理,自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。
http://www.mscto.com

  2.用事件对象实现通信

  在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下:

////////////////////////////////////////////////////////////////////
CeventthreadStart,threadEnd;
////////////////////////////////////////////////////////////////////
UINTThreadFunction(LPVOIDpParam)
{
::WaitForSingleObject(threadStart.m_hObject,INFINITE);
AfxMessageBox("Threadstart.");
while(!bend)
{
Beep(100,100);
Sleep(1000);
Intresult=::WaitforSingleObject(threadEnd.m_hObject,0);
//等待threadEnd事件有信号,无信号时线程在这里悬停
If(result==Wait_OBJECT_0)
Bend=TRUE;
}
::PostMessage(hWnd,WM_USERMSG,0,0);
return0;
}
/////////////////////////////////////////////////////////////
VoidCtestView::OninitialUpdate()
{
hWnd=GetSafeHwnd();
threadStart.SetEvent();//threadStart事件有信号
pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程
pThread->m_bAutoDelete=FALSE;
Cview::OnInitialUpdate();
}
////////////////////////////////////////////////////////////////
VoidCtestView::OnDestroy()
{threadEnd.SetEvent();
WaitForSingleObject(pThread->m_hThread,INFINITE); 软件开发网 
deletepThread;
Cview::OnDestroy();
}

运行这个程序,当关闭程序时,才显示提示框,显示"Threadended"
四、线程之间的同步

  前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。VisualC
中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,MFC支持的七个多线程的同步类可以分成两大类:同步对象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象(CmultiLock和CsingleLock)。本节主要介绍临界区(criticalsection)、互斥(mutexe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安全。
软件开发网

  1.临界区

  临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程,它们对应的函数分别为WriteThread()和ReadThread(),用以对公共数组组array[]操作,下面的代码说明了如何使用临界区对象:

#include"afxmt.h"
intarray[10],destarray[10];
CCriticalSectionSection;
////////////////////////////////////////////////////////////////////////
UINTWriteThread(LPVOIDparam)
{Section.Lock();
for(intx=0;x<10;x
)
array[x]=x;
Section.Unlock();
}
UINTReadThread(LPVOIDparam)
{
Section.Lock();
For(intx=0;x<10;x
)
Destarray[x]=array[x];
Section.Unlock();
} http://www.mscto.com

上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下。
 2.互斥
软件开发网

  互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与Cmutex类的对象相对应,使用互斥对象时,必须创建一个CSingleLock或CMultiLock对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下:

http://www.mscto.com

#include"afxmt.h"
intarray[10],destarray[10];
CMutexSection;

/////////////////////////////////////////////////////////////
UINTWriteThread(LPVOIDparam)
{CsingleLocksinglelock;
singlelock(&Section);
singlelock.Lock();
for(intx=0;x<10;x
)
array[x]=x;
singlelock.Unlock();
}
UINTReadThread(LPVOIDparam)
{CsingleLocksinglelock;
singlelock(&Section);
singlelock.Lock();
http://www.mscto.com

For(intx=0;x<10;x
)
Destarray[x]=array[x];
singlelock.Unlock();

} 软件开发网

  3.信号量

  信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个CsingleLock或CmltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。下面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才得以显示。

/////////////////////////////////////////////////////////////////
Csemaphore*semaphore;
Semaphore=newCsemaphore(2,2);
HWNDhWnd=GetSafeHwnd();
AfxBeginThread(threadProc1,hWnd);
AfxBeginThread(threadProc2,hWnd);
AfxBeginThread(threadProc3,hWnd);
//////////////////////////////////////////////////////////////////////
UINTThreadProc1(LPVOIDparam)
{CsingleLocksingelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread1hadAccess","Thread1",MB_OK);
return0;
}
UINTThreadProc2(LPVOIDparam)
{CSingleLocksingelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread2hadaccess","Thread2",MB_OK);
return0;
}
UINTThreadProc3(LPVOIDparam)
{CsingleLocksingelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread3hadaccess","Thread3",MB_OK);
return0;
}

http://www.mscto.com

  对复杂的应用程序来说,线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本文讲述了线程中经常遇到的问题,希望对读者朋友有一定的帮助。 http://

时间: 2024-10-14 03:58:47

Windows多线程多任务设计初步(转)的相关文章

windows多线程同步

概述 任何单个应用程序都不能完全使该处理器达到满负荷.当一个线程遇到较长等待时间事件时,同步多线程还允许另一线程中的指令使用所有执行单元.例如,当一个线程发生高速缓存不命中,另一个线程可以继续执行.同步多线程是 POWER5? 和 POWER6? 处理器的功能,可与共享处理器配合使用. SMT 对于商业事务处理负载的性能优化可达30%.在更加注重系统的整体吞吐量而非单独线程的吞吐量时,SMT 是一个很好地选择. 但是并非所有的应用都能通过SMT 取得性能优化.那些性能受到执行单元限制的应用,或者

【APUE】关于windows多线程编程的学习笔记

保证在某一时刻只有一个线程对数据进行操作的基本方法: (1)关中断:通过关闭时钟中断来停止线程调度(不现实) (2)数学互斥方法:Peterson算法 bakery算法 (3)操作系统提供的互斥方法:临界区.互斥量.信号量等(windows) (4)cpu原子操作:把一些常用的指令设计成了原子指令,在windows上面也被称为原子锁 [APUE]关于windows多线程编程的学习笔记

python多线程爬虫设计及实现示例

爬虫的基本步骤分为:获取,解析,存储.假设这里获取和存储为io密集型(访问网络和数据存储),解析为cpu密集型.那么在设计多线程爬虫时主要有两种方案:第一种方案是一个线程完成三个步骤,然后运行多个线程:第二种方案是每个步骤运行一个多线程,比如N个线程进行获取,1个线程进行解析(多个线程之间切换会降低效率),N个线程进行存储. 下面我们尝试抓取http://www.chembridge.com/ 库存药品信息. 首先确定url为http://www.chembridge.com/search/se

多线程多任务是程序开发者与用户都需要的中资产

电脑工业界每有新的技术问世,人们总是不遗余力地去担忧"它是不是够 重要".公司行号虎视眈眈地注意其竞争对手,直到对方采用并宣扬这技术有 多么重要,才开始急急赶上.不论这技术是不是真的很重要,每一个人都想尽 办法让最终用户感觉"真的很重要".好啦,于是最终用户真的觉得需要它 了--即使他们完全不了解那是什么东西. "线程"程序设计正处在这个循环的起点.虽然线程在各式各样的操作系 统上已经存在了不只十年,但它毕竟还是藉着无孔不入的 Windows 9

Windows多线程编程总结

Windows 多线程编程总结 keyword:多线程 线程同步 线程池 内核对象 1 内核对象 1 .1 内核对象的概念 内核对象是内核分配的一个内存块,这样的内存块是一个数据结构,表示内核对象的各种特征.而且仅仅能由内核来訪问.应用程序若须要訪问内核对象,须要通过操作系统提供的函数来进行,不能直接訪问内核对象( Windows 从安全性方面来考虑的). 内核对象通过 Create* 来创建,返回一个用于标识内核对象的句柄,这些句柄 (而不是内核对象)可在创建进程范围内使用,不可以被传递到其它

Android 多线程多任务下载框架的实现(一)

什么是多线程多任务下载框架: Android 多线程多任务下载框架 封装了一个下载工具类,该下载工具支持多线程下载,下载任务队列,下载进度更新,取消下载等.可用于应用市场app的下载,音乐下载等. 为什么需要多线程多任务下载框架: 我们在开发Android 应用市场的时候需要下载apk,这个时候用户希望能同时下载多个apk并且显示正确的进度条信息,且下载速度快,那么我们就需要多线程多任务下载框架来支持这些功能,来达到很好的用户体验. 怎么实现多线程多任务下载框架: 涉及到的知识点: 1.线程操作

模块管理常规功能自定义系统的设计与实现(11--Grid导航设计初步[1])

Grid导航设计(初步)[1] 这一节讲一下Grid导航设计.在前面的章节中有看到Grid导航的样子,那是一个模块的父模块(ManyToOne)对子模块的导航操作.现在对于一个"省份"模块,其没有父模块,我们能对其设计成怎么样的导航呢? 只需一步,将一个字段定义为导航字段.进入"模块字段" 点击修改 保存后,刷新页面.再进入"省份模块"后,可以看到grid的左边就会有一个导航列表. 具选中某个导航值,Grid列表中显示的值就会以此值作为条件进行约

windows多线程接口介绍和使用

一windows多线程接口: 1 创建线程 CreateThread 与 _beginthreadex都可以实现创建线程,两个函数的参数 相同, HANDLEWINAPICreateThread( LPSECURITY_ATTRIBUTESlpThreadAttributes, SIZE_TdwStackSize, LPTHREAD_START_ROUTINElpStartAddress, LPVOIDlpParameter, DWORDdwCreationFlags, LPDWORDlpThr

Windows多线程编程及常见问题

提要: Windows 多线程Helloworld 以Windows代码为例,分析多线程编程中易出现的问题 Windows多线程的Helloworld: 笔者写过Java多线程的程序(实现Runnable接口,利用Thread类执行),也写过Linux多线程程序(利用pthread).最近由于另有需要使用Windows多线程,由于Windows API历来难用,特此记录,以作备忘. Helloworld源代码如下: 1 #include <stdio.h> 2 #include <win