本演示程序中代码如下

Download Report

Transcript 本演示程序中代码如下

基于VC多线程演示程序
----王红





引入
主要任务
基本函数的介绍及应用
功能模块设计
总结
进程的引入
程序(Program)是一组指令的有序集合,它本身没有任何
运行的含义,它只是一个静态的实体。
也就是说,用程序这个静态的实体不能反映程序在并发执行
过程的这些动态特征。于是,人们引入“进程(Process)“这一
概念来描述程序动态执行过程的性质。
进程是应用程序的执行实例,是操作系统分配资源单位。
进程最根本的属性是动态性和并发性。
每个进程是由私有的虚拟地址空间、代码、数据和其它各种
系统资源组成,进程在运行过程中创建的资源随着进程的终止
而被销毁,所使用的系统资源在进程终止时被释放。
线程的引入
由于每创建一个进程需要申请许多的系统资源,比如内存空
间分配,PCB的分配等,这样就会造成一些不必要的浪费,而线
程不需要任何系统资源,它与所属的进程共享系统资源,并且线
程之间的切换速度快,提高了程序运行的效率,所以引入了线程
的概念。
线程是操作系统分配处理器的最基本单元,它是操作系统用
来调度执行的最小单位。
一个线程可以创建和撤销另一个线程;同一个进程中的多个线
程之间可以并发执行。
线程的状态及其转化
运行
时间片
用完
分配到
CPU
就绪
事件发生
等待
事件
阻塞
线程之间的通信
在多线程的环境里,需要对线程之间的通信进行同步。常用的同步
对象:
1、临界区
通过提供所有线程必须共享的对象来控制线程,只有拥有临界区的
线程才可以访问受保护资源。
2、互斥量
互斥量和临界区的工作方式相似,区别在于互斥量不仅保护一个进
程内的资源共享,而且还保护系统中线程之间的共享资源。
3、信号量
信号量与互斥量相似,但是互斥只允许在同一时刻一个线程访问它
的数据,而信号量允许多个线程在同一时刻访问它的数据。
4、事件
事件对象用于给线程传递信号,指示线程中特定的操作可以开始或
结束。
主要完成的任务
本演示程序主要是对下面的模型实例化,利用图形界面直
观易懂的特点,把完全抽象的线程的就绪、阻塞(等待)、执
行的状态以及同步互斥的过程用图形动态的显示出来。
本演示程序使用的开发工具是VC++6.0,基于MFC类库的。
用信号量和事件作为线程之间的同步互斥工具,通过对设置
BUFFER1、BUFFER2的容量,来协调PUT、GET、MOVE这
三个线程,使其同步来实现P、V操作。本程序并非真正的传输
数据,只是对一个数据计数器加减来模拟数据的增加减少,然
后通过定时刷新,将线程的状态、数据显示到界面上。
BUFFER1
PUT
BUFFER2
MOVE
GET
主要用到的函数
1、 P/V操作函数
P/V操作就是用一个信号量S来实现线程之间的通信。信号量S
是一个由P操作和V操作改变其值的整型变量。
P操作一次,S值减1,即S=S-1(请求分配一资源),如果S≥0,
则该线程继续执行;如果S<0表示无资源,则该线程的状态置为等
待状态,并把该线程加入到信号量等待队列。
V操作一次,S值加1,即S=S+1(释放一资源)如果S>0,表示
有资源,则该线程继续执行;如果S≤0,则释放信号量队列上的第
一个线程(等待状态改为就绪状态),执行V操作的线程继续运行。
本演示程序用到的P/V操作函数是:
#define p(s) ::WaitForSingleObject(s,INFINITE)
#define v(s) ::ReleaseSemaphore(s,1,NULL)
2、等待函数
DWORD WaitForSingleObjec(
HANDLE hHandle,//对象句柄
DWORD dwMilliseconds//等待的时间,单位为毫秒)
第一个参数是等待的同步对象的句柄,第二个参数为等待
时间。
等待函数返回:同步对象获得信号时返回;等待时间达到
了返回。如果等待时间不限制(Infinite),则只有同步对象获得
信号才返回,在等待期间会将线程挂起,直到等待的信号量有
信号进入就绪状态。
3、 MFC中的CSemaphore类(信号量)
当需要一个计数器来限制可以使用某个资源的线程数目时,可以
使用信号量对象,即CSemaphore类的对象。
CSemaphore的一个对象保存了对当前访问某一指定资源的线程
的计数值,该计数值是当前还可以使用该资源的线程的数目。
1)信号量创建函数:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES
lpSemaphoreAttributes, // 安全属性指针
LONG lInitialCount, // 初始计数
LONG lMaximumCount, // 最大计数
LPCTSTR lpName // 对象名指针)
2)信号量释放函数
BOOL ReleaseSemaphore(
HANDLE hSemaphore, // 信号量句柄
LONG lReleaseCount, // 释放数量
LPLONG lpPreviousCount // 先前计数 )
本演示程序主要用到了6个信号量,如下:
1)empty1表示BUFFER1是否有空间。
2)full1表示BUFFER1是否有数据。
3)buffer1表示BUFFER1是否可操作。
4)empty2表示BUFFER2是否有空间。
5)full2表示BUFFER2是否有数据。
6)buffer2表示BUFFER2是否可操作。
程序中代码如下:
empty1=CreateSemaphore(0,M,M,0);
full1=CreateSemaphore(0,0,M,0);
buffer1=CreateSemaphore(0,1,1,0);
buffer2=CreateSemaphore(0,1,1,0);
empty2=CreateSemaphore(0,N,N,0);
full2=CreateSemaphore(0,0,N,0);
4、使用CEvent 类
CEvent类提供了对事件的支持。事件是一个允许一个线程在某种情
况发生时,唤醒另外一个线程的同步对象。事件告诉线程何时去执行某
一给定的任务。
在MFC中,CEvent类对象有两种类型:人工事件和自动事件。
一个自动的CEvent对象在被至少一个线程释放后会自动返回到无信
号状态;而人工的CEvent对象获得信号后,释放可利用线程,但直到调
用成员函数ReSetEvent()才将其设置为无信号状态。在创建CEvent类
的对象时,默认创建的是自动事件。本程序使用的是人工事件。
1)CEvent函数的创建
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性
BOOL bManualReset, //事件类型
BOOL bInitialState, // 初始状态
LPCTSTR lpName // 对象名称 );
参数bManualReset指定要创建的事件是属于人工事件还是自动事件,
TRUE为人工事件,FALSE为自动事件; 参数bInitiallyOwn指定事件对
象初始化状态,TRUE为有信号,FALSE为无信号;第一个和最后一个
参数分别表示CEvent对象的安全属性和名称,一般设为NULL。
2)改变CEvent对象状态的函数
(1)SetEvent()函数
该函数将CEvent类对象的状态设置为有信号状态,并且释放所
有等待的线程。原型为:
BOOL SetEvent();
如果该函数执行成功,则返回非零值,否则返回零。
(2)ResetEvent()函数
该函数将事件的状态设置为无信号状态,并保持该状态直至
SetEvent()被调用时为止,由于自动事件是由系统自动重置,故自
动事件不需要调用该函数。该函数的原型为:
BOOL ResetEvent();
如果该函数执行成功,返回非零值,否则返回零。
本演示程序代码如下:
hEvent=::CreateEvent(0,TRUE,FALSE,0);(初始化时)
5、线程函数
1)线程的创建
MFC中有两类线程,分别称之为工作者线程和用户界面线程。两
者的主要区别在于:工作者线程没有消息循环,而用户界面线程有自
己的消息队列和消息循环。本演示程序使用的是工作者线程。
创建一个工作者线程,首先需要编写一个希望与应用程序的其余
部分并行运行的自定义函数,该函数称为线程函数。然后,在程序中
合适的地方调用全局函数AfxBeginThread() 启动线程函数。
线程函数的固定形式:
UINT FunctionName (LPVOID pParam)
FunctionName是用户自定义的函数名,LPVOID表示指向空类型的
指针,相当于void*,必要时需要把这个指针转换成所需要的类型。
本演示程序创建了三类线程函数,通常线程函数里是个大的WHILE循
环,让线程一直处于运行当中。
(1)PUT线程函数,向BUFFER1里放数据,相当于生产者;代码如下:
UINT put(LPVOID lparam)
{
while(!dlg->bkill)
{
p(dlg->hEvent);
p(dlg->empty1);
p(dlg->buffer1);
dlg->m_list.SetItemText(dlg->buffer1number , 1, "----");
dlg->m_shu1+=1;
shu1.Format("%d",dlg->m_shu1);
dlg->GetDlgItem(IDC_SHU1)->SetWindowText(shu1);
dlg->buffer1number++;
dlg->allnumber1++;
dlg->bput=TRUE;
::Sleep(dlg->delay1);
v(dlg->buffer1);
v(dlg->full1);
if(dlg->bPause =FALSE)
{dlg->m_status .SetWindowText ("空闲");}
else
{dlg->m_status .SetWindowText ("执行");}
dlg->bput=FALSE;
}
}
(2) MOVE线程函数,从BUFFER1里取数据并放到BUFFER2
里,相当于搬运者;
UINT move(LPVOID lparam)
{
while(!dlg->bkill)
{
p(dlg->hEvent,INFINITE);
p(dlg->full1);
p(dlg->empty2);
p(dlg->buffer1);
p(dlg->buffer2);
dlg->m_list.SetItemText(dlg->buffer2number , 3, "----");
dlg->m_shu2+=1;
shu2.Format("%d",dlg->m_shu2);
dlg->GetDlgItem(IDC_SHU2)->SetWindowText(shu2);
dlg->buffer1number--;
dlg->buffer2number++;
dlg->m_list.SetItemText(dlg->buffer1number , 1, "");
dlg->m_shu1-=1;
shu1.Format("%d",dlg->m_shu1);
dlg->GetDlgItem(IDC_SHU1)->SetWindowText(shu1);
dlg->bmove=TRUE;
::Sleep(dlg->delay2);
v(dlg->buffer1);
v(dlg->buffer2);
v(dlg->full2);
v(dlg->emtpy1);
}
}
(3)GET线程函数,从BUFFER2里取数据,相当于消费者。
UINT get(LPVOID lparam)
{
while(!dlg->bkill)
{
p(dlg->hEvent,INFINITE);
p(dlg->full2);
p(dlg->buffer2);
dlg->buffer2number-=1;
dlg->m_list.SetItemText(dlg->buffer2number , 3, "");
dlg->m_shu2-=1;
shu2.Format("%d",dlg->m_shu2);
dlg->GetDlgItem(IDC_SHU2)->SetWindowText(shu2);
dlg->allnumber2++;
dlg->bget=TRUE;
::Sleep(dlg->delay3);
v(dlg->buffer2);
v(dlg->empty2);
if(dlg->bPause =FALSE)
{ dlg->m_status .SetWindowText ("空闲");}
else
{dlg->m_status .SetWindowText ("执行");}
dlg->bget=FALSE;
}
}
2)启动线程函数
CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs= NULL
);
pfnThreadProc是一个指向线程函数的指针,参数pParam的类型与
线程函数的参数类型完全一致,该参数为启动线程时传递给线程函数
的入口参数。其他几个参数用于设置线程优先级、线程的堆栈大小、
创建时是否立即运行及线程的安全属性,这4个参数通常使用默认值
。
本演示程序中代码如下:
AfxBeginThread(put,this);
AfxBeginThread(move,this);
AfxBeginThread(get,this);
功能模块设计
1.
2.
3.
4.
登录模块
注册模块
后台管理模块
多线程演示模块
1 登录模块
该模块有两个权限,分别是用户和管理员 ,选择不同的
权限,分别进入相应的模块,即多线程演示模块和后台管理
模块。如果用户未注册,可以单击注册按钮,进入注册模块
2 注册模块
用户可以在该模块进行注册。注册完,通过登录界面登录,
然后进入多线程演示程序。注释:该模块只能注册普通用户。
3 后台管理模块
该模块包括:
 管理员管理:对管理员账号和密码进行修改等。
 用户管理:对用户名和密码进行查找、修改、删除等操
作。
 历史记录管理:对用户演示的历史信息进行管理。
 退出:退出后台管理模块,回到登录模块。
4 多线程演示模块
该模块包括:
1)历史记录:可以查看该用户的历史演示信息。
2)参数设置:显示参数设置对话框,对生产者、搬运者、消费
者的线程数进行设置,对BUFFER1和BUFFER2容量进
行设置及对生产者、搬运者、消费者线程的速度进行设置
等。
3)开始:启动线程,进行演示。
4)暂停/继续:判将线程挂起或唤醒,观察线程状态。暂停功
能只有在线程运行过程中使用,它将所有的线程全部挂起,
点击此按钮后,【暂停】按钮变成【继续】按钮;点击
【继续】按钮将使所有的线程唤醒,继续执行。
5)停止/保存:结束所有线程的运行;并提示是否进行运行完
毕后的记录保存,保存运行后的状态。
6)退出:结束演示, 退出演示程序,回到登录界面。
箭头表示线程数,直线表示BUFFER里的数据。
总结
本演示程序将P、V操作模型通过编程实现,用计数器来表示
BUFFER中数据个数,箭头表示线程数,直线表示BUFFER里的
数据。为了能够直观的理解线程的同步和互斥,用户可以自行设
定每类线程的个数和BUFFER的容量,并可以改变线程的运行速
度。通过本程序的演示,使我们更直观、更深入的明确操作系统
中进程的概念,能够知道为什么要使用多道程序的思想,以及使
线程同步的基本方法,掌握基本的VC编程,提高动手能力。
谢 谢!