当前位置:文档之家› 操作系统-创建线程,利用互斥实现线程共享变量通信

操作系统-创建线程,利用互斥实现线程共享变量通信

操作系统-创建线程,利用互斥实现线程共享变量通信
操作系统-创建线程,利用互斥实现线程共享变量通信

创建线程,利用互斥实现线程共享变量通信

一.概述

1.1 课题目的和意义

掌握线程创建和终止,加深对线程和进程概念的理解,会用同步与互斥方法实现线程之间的通信。

1.2内容和要求

软件界面上点“创建线程”按钮,创建三个生产者线程(P1,P2,P3)和两个消费者线程(C1,C2),生产者和消费者线程共享一个长度为2KB的环型公共缓冲区,生产者向其中投放消息,消费者从中取走消息。只要缓冲区未满,生产者可将消息送入缓冲区;只要缓冲区未空,消费者可从缓冲区取走一个消息。

每个消息具下列结构格式:

消息头(1B,固定为0xaa),消息长度(1B),消息内容(nB),校验和(1B),检验和计算方式为消息长度和消息内容所有字节异或结果。

每个生产者每隔n毫秒(n用随机数产生,1到100毫秒之间,间隔不固定)生产一个消息加入缓冲区,并把消息产生时间和内容记录在一个文本文件中(或显示在列表框中)。P1每次生产的数据为26个大写字母,P2每次生产的数据为26个小写字母,P3每次生产的数据为10个数字。

每个消费者每隔n秒(n用随机数产生,1到5秒之间,间隔不固定)从缓冲区取走一个消息。每消费一个消息需要将消费时间和消息内容记录在一个文本文件中(或显示在列表框中)。

当用户按结束按钮时结束5个线程,并将5个文件内容显示出来进行对照。

这期实是一个经典的生产者—消费者(Producer_consumer)进程(线程)同步的问题。它描述的是:有一群生产者进程在生产产品,并将此产品提供给消费者进程(线程)去消费。为使生产者进程和消费者进程(线程)能并发执行,在它们之间设置有个缓冲区的缓冲池,生产者进程(线程)可将它所生产的产品放入一个缓冲区中,消费者进程(线程)可从一个缓冲区取得一个产品消费。尽管所有的生产者进程和消费者进程(线程)都是以异步的方式运行的,但它们之间必须保持同步,即不允许消费者进程(线程)到一个空缓冲区去取产品,也不允许生产者进程(线程)向一个已装有消息尚未被取走产品的缓冲区投放产品。如下图所示:

1.3线程所采用的同步方法

同步是多线程中的重要概念.同步的使用可以保证在多线程运行的环境中,程充不会产生设计之外的结果.同步的实现方式有两种,同步方法和同步块.

线程在执行同步方法是具有排它性的.当任意一个线和进入到一个对象的任意一个同步方法时,这个对象所有同步方法都被锁定,在些期间,期他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导至它释放了该对象的同步锁这后.在一个对象被某个线程锁定之后,其他线程是可以访问.

同步的有几种实现方法,分别是:

wait():使一个线程处于等待状态,并且释放所有持有的对象lock.

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。

notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

1.4开发工具平台

开发平台:window XP 开发工具:VC++

二.数据定义和详细说明

1数据定义

设计PV操作算法,用信号量机制实现生产者与消费者同步与互斥问题,并与无PV情况下进行对比。定义20个缓冲区,将其初始化为0。调用随机函数rand()生成随机数,把随机数通过生产者放入缓冲区。若缓冲区满则其值非0。当消费者从缓冲区中去数后缓冲区值变为0。程序可显示缓冲区中的全部内容,方便观察生产者与消费者的行为。程序可通过设置sleep(time)中time的值来控制生产者与消费者的执行顺序。

2详细说明

为了实现生产者与消费者同步与互斥的问题,该程序用记录型信号量机制来实现。

假定在生产者与消费者之间,利用一个公共的缓冲池来进行通信,生产者将所生产的信息放入其中,消费者cognitive缓冲池中取得消息来消费,该缓冲池具有n个缓冲区,其编号为0,1,2,3···,n-1;设置一个互斥信号量mutex,用于实现诸进程对缓冲池的互斥使用,其初值为1,利用资源信号量empty,表示缓冲池中空缓冲区的数目,其初值为n;full分别表示缓冲池中满缓冲区的数目,其初值为0.又假定这些生产者和消费者相互等效,只要缓冲池未满,生产者便可将消息送入由指针in所指的缓冲区;只要缓冲池未空,消费者变可以从由指针out所指示的缓冲区中,取走一个消息。对生产者消费者的问题可以描述如下:

Var metux,empty,full:semaphore=1,n,0;

Buffer:array[0,···n-1] of item;

In,out:integer:=0;

Begin:

Pabegin

Producer:begin

Repeat

Producer an item nextp;

P(empty);

P(mutex);

buffer(in):=nextp;

in:=(in+1)mod n;

V(mutex);

V(full);

Until false;

End

Consumer:begin

Repeat

P(full);

P(mutex);

nextc:= buffer(out);

out:=(out+1)mod n;

V(mutex);

V(empty);

Until false;

End

Parend

End

三.实现思想和设计流程

1实现思想

我们把系统中使用某一类资源的进程(线程)称为该资源的消费者,而把释放同类资源的进程称为该资源的生产者。例如在计算进程(线程)与打印进程(线程)公用一个缓冲区时,计算进程(线程)把数据送入缓冲区,打印进程(线程)从缓冲区中取数据打印输出,因此,计算进程相当于数据资源的生产者,而打印进程相当于消费者,二者之间必须保持同步。基于这一问题,我们将使用生产者和消费者这一同步机制算法来处理该问题.

2设计流程

首先,我们知道,生产者—消费者问题是一个同步问题。即生产者和消费者之间应满足如下条件:

2.1 消费者想接收数据时,有界缓冲区中至少有一个单元是满的。

2.2 生产者想发送数据时,有界缓冲区中至少有一个单元是空的。

另外,由于有界缓冲区是临界资源,因此,各生产者进程和各消费者进程之间必须互斥。其次,我们还必须考虑面临的问题是属于进程互斥还是进程同步,或是互斥与同步的混合问题。然后根据共享资源的数量以及使用共享资源的规则正确的定义信号量及其初值。

最后,还要对结果进行分析处理。若结果中生产和消费进程都已处理完时,但还可能出现以下两种情况:一是还有生产进程,但没有空缓冲,且消费进程暂时已完,所以此时,只能结束等待新的消费进程产生空缓冲。二是还有消费进程,但没有了满缓冲,且生产进程暂时已完,此时,只能结束等待新的生产进程来输入数据,产生新的满缓冲等。在程序中应能作出相应的判断和处理。

本程序的执行是在C++的环境中通过手动输入生产者和消费者线程的运行速度来控制程序的运行的。为了实现生产者进程能把生产出来的产品正确的存入缓冲区,和消费者进程能够从缓冲区中取产品进行消费,防止因等待资源而出现死锁的现象,首先设置两个时间:生产者生产一个产品后等待的时间t1,和消费者消费一个产品后等待的时间t2,来控制生产者和消费者进程执行的速度。其函

数原形是sleep(t1)和sleep(t2)其中t1、t2指定义挂起执行线程的时间,以毫秒为

单位,取值为0时,该线程将余下的时间片交给处于就绪状态的同一优先级的其他线程。若没有处于就绪状态的同一优先级的其他线程,则函数立即返回。

3 程序流程图

4关键代码分析

本程序采用了MFC可视化界面来完成,现在给出关键代码来分析.

4.1开始创建线程

原代码:

void CMultiThreadDlg::OnStart() //开始创建线程

{ Y

hMutex=CreateMutex(NULL,FALSE,NULL);//创建互斥对象

threadController=1;

check=TRUE;//检测标识

HWND hWnd=GetSafeHwnd();//得到控制权

AfxBeginThread(ThreadProc,hWnd,THREAD_PRIORITY_NORMAL);//启用生产者线程1(P1) AfxBeginThread(ThreadProc2,hWnd,THREAD_PRIORITY_NORMAL);//启用生产者线程2(P2)

AfxBeginThread(ThreadProc3,hWnd,THREAD_PRIORITY_NORMAL);//启用生产者线程3(P3)

AfxBeginThread(Thread_consumer,hWnd,THREAD_PRIORITY_NORMAL);//启用消费者线程1(S1)

AfxBeginThread(Thread_consumer2,hWnd,THREAD_PRIORITY_NORMAL);//启用消费者线程2(S2)

}

原码功能:主要创建生产者和消费者线程,创建互斥对象,创建窗体对象.

主要函数功能:

4.1.2 CreateMutex()

函数功能:该函数是创建有名或者无名的互斥对象。

函数原型:

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,

BOOL bInitialOwner, LPCTSTR lpName);

参数:lpMutexAttributes:指向SECURITY_ATTRIBUTES结构的指针,该结构决定子进程是否能继承返回句柄。如果lpMutexAttributes为NULL,那么句柄不能被继承。

在Windows NT中该结构的lpSecurityDescriptor成员指定新互斥对象的安全描述符。如果lpMutexAttributes为NULL,那么互斥对象获得缺省的安全描述符。

bInitialOwner:指定互斥对象的初始所属身份。如果该值为TRUE并且调用者创建互斥对象,那么调用线程获得互斥对象所属身份。否则,调用线程不能获得互斥对象所属身份。判断调用者是否创建互斥对象请参阅返回值部分。

lpName:指向以NULL结尾的字符串,该字符串指定了互斥对象名。该名字的长度小于MAX_PATH且可以包含除反斜线路径分隔符(\ )以外的任何字符。名字是区分大小写的。

如果与已存在的有名互斥对象名相匹配,那么该函数要求用__权限访问已存在的对象。在这种情况下,由于参数己被创建进程所设置,该参数被忽略。如果参数不为,它决定句柄是否解除继承,但是其安全描述符成员被忽略。

如果lpName为NULL,那么创建的互斥对象无名。

如果lpName与已存在的事件、信号量、可等待定时器、作业、或者文件映射对象的名字相匹配,那么函数调用失败,并且GetLastError函数返回ERPOR_INV ALID_HANDLE。其原因是这些对象共享相同的名字空间。

返回值:如果函数调用成功,返回值是互斥对象句柄;如果函数调用之前,有名互斥对象已存在,那么函数给已存在的对象返回一个句柄,并且函数GetLastError返回

ERROR_ALREADY_EXISTS,否则,调用者创建互斥对象。

如果函数调用失败,则返回值为NULL。若想获得更多错误信息,请调用GetLastError函数。

如果CreateMutex中的lpMutexAttributes参数允许继承,由CreateProcess函数创建的子进程可以继承父进程的互斥对象句柄。

一个进程可以在调用DuplicateHandle函数时指定互斥对象句柄来创建一个可以被其他进程使用的双重句柄。一个进程在调用OpenMutex或CreateMutex函数时能指定互斥对象名。

使用CloseHandle函数关闭句柄,进程结束时系统自动关闭句柄。当最后一

个句柄被关闭时,互斥对象被销毁。

4.1.3 GetSafeHwnd()

功能:到一个窗口对象(CWnd的派生对象)指针的句柄(HWND)

函数原型: HWND hwnd = pwnd->GetSafeHwnd();

函数用法:

CWnd *pwnd = FindWindow(“ExploreWClass”,NULL); //希望找到资源管理器

HWND hwnd = pwnd->m_hwnd; //得到它的HWND

这样的代码当开始得到的pwnd为空的时候就会出现一个“General protection error”,并关闭应用程序,因为一般不能对一个NULL指针访问其成员,如果用下面的代码:

CWnd *pwnd = FindWindow(“ExploreWClass”,NULL); //希望找到资源管理器

HWND hwnd = pwnd->GetSafeHwnd(); //得到它的HWND

就不会出现问题,因为尽管当pwnd是NULL时,GetSafeHwnd仍然可以用,只是返回NULL,通过GetSafeHwnd()的实现代码就更清楚了:

_AFXWIN_INLINE HWND CWnd::GetSafeHwnd() const

{

return this == NULL?NULL:m_hWnd;

}

4.1.4 AfxBeginThread()

功能:用于创建工作者线程

函数原型:CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc,LPVOID pPara m,int nPriority = THREAD_PRIORITY_NORMAL,UINT nStackSize = 0,DWORD dwCreateFlags = 0,LPSECURITY_ATTRIBUTES lpSecurityAttrs= NULL);

返回值: 一个指向新线程的线程对象.

pfnThreadProc : 线程的入口函数,声明一定要如下: UINT MyThreadFunction( LPVOID pParam );

pParam : 传递入线程的参数,注意它的类型为:LPVOID,所以我们可以传递一个结构体入线程. nPriority : 线程的优先级,一般设置为0 .让它和主线程具有共同的优先级.

nStackSize : 指定新创建的线程的栈的大小.如果为0,新创建的线程具有和主线程一样的大小的栈

dwCreateFlags : 指定创建线程以后,线程有怎么样的标志.可以指定两个值:

CREATE_SUSPENDED : 线程创建以后,会处于挂起状态,直到调用:ResumeThread0: 创建线程后就开始运行.

lpSecurityAttrs:指向一个SECURITY_ATTRIBUTES 的结构体,用它来标志新创建线程的安全性.如果为NULL,那么新创建的线程就具有和主线程一样的安全性.如果要在线程内结束线程,可以在线程内调用AfxEndThread.

4.2生产者线程

原码:

UINT CMultiThreadDlg::ThreadProc(LPVOID param)//生产者1的生产过程

{

while(threadController)

{

WaitForSingleObject(hMutex,INFINITE);//等待一个同步事件的到来

if(number<20)

{

srand ((unsigned)time(NULL));//返回一个随机数

int n;

n=rand()%9+1;//随机显示字数

Sleep(n*100);

CString str;

CString str_;

for(int i=0; i

{

str_.Format("%c",65+rand()%9+1);//显示大写字母

str.Insert(i,str_);//插入成一列

}

list.AddTail(CString(str));

CString string;

string.Format("%s%d%s%d","0xaa",n,list.GetTail(),sizeof(list.GetTail())^sizeof(n));//按格式消息头(1B,固定为0xaa),消息长度(1B),消息内容(nB),校验和(1B)

pbox1->AddString(string);//打印

number++;

}

ReleaseMutex(hMutex);//释放互斥

}

}

原码功能:当生产者线程1得到控制权,等待同步事件,产生随机个数(n),按产生的随机数显示n个大写字母,利用sleep函数来隔时存储到缓冲区,最后按格式消息头(1B,固定为0xaa),消息长度

(1B),消息内容(nB),校验和(1B)的方式输出.

主要函数功能:

4.2.1 WaitForSingleObject()

函数功能:当如下情况之一发生时该函数返回:指定对象处于信号态;超时。

函数原型:

DWORD WaitForSingleObject( HANDLE hHandle,DWORD dwMilliseconds);

参数:hHandle:等待对象句柄。若想了解指定句柄的对象类型列表,参阅下面说明部分。

在WndowsNT中,句柄必须有SYNCHRONIZE访问权限。若想获得更多的信息,请查看Standard Access Rights。

dwMilliseconds:指定以毫秒为单位的超时间隔。如果超时,即使对象的状态是非信号态的并且没有完成,函数也返回。如果dwMilliseconds是0,函数测试对象的状态并立刻返回:如果dwMillseconds是INFINlTE,函数从不超时。

返回值:如果函数调用成功,返回值表明引起函数返回的事件。可能值如下:

WAIT_ABANDONED:指定对象是互斥对象,在线程被终止前,线程没有释放互斥对象。互斥对象的所属关系被授予调用线程,并且该互斥对象被置为非信号态。

WAIT_OBJECT_0:指定对象的状态被置为信号态。

WAlT_TIMEOUT:超时,并且对象的状态为非信号态。

如果函数调用失败,返回值是WAIT_FAILED。若想获得更多错误信息,请调用GetLastError 函数。

4.2.2 ReleaseMutex()

函数功能:该函数放弃指定互斥对象的拥有权。

函数原型:BOOL ReleaseMutex(HANDLE hMutex);

参数:hMutex:互斥对象句柄。为CreateMutex或OpenMutex函数的返回值。

返回值:如果函数调用成功,那么返回值是非零值;如果函数调用失败,那么返回值是零值。若想获得更多错误信息,请调用GetLastError函数。

备注:如果调用线程不拥有互斥对象,ReleaseMutex函数失败。

一个线程通过调用等待函数拥有互斥对象。创建该互斥对象的线程也拥有互斥对象,而不需要调用等待函数。当互斥对象的所有者线程不再需要互斥对象时,它可以调用ReleaseMutex 函数。

当一个线程拥有一个互斥对象后,它可以用该互斥对象多次调用等待函数而不会阻塞。这防止一个线程等待一个它已拥有的互斥对象时出现死锁。不过,为了释放所有权,该线程必须为每一个等待操作调用一次ReleaseMutex函数。

4.2.3 Sleep()

函数功能:该函数对于指定的时间间隔挂起当前的执行线程。

函数原型:void Sleep(DWORD dwMilliseconds);

参数:dwMilliseconds:定义挂起执行线程的时间,以毫秒为单位。取值为0时,该线程将余下的时间片交给处于就绪状态的同一优先级的其他线程。若没有处于就绪状态的同一优先级的其他线程,则函数立即返回,该线程继续执行。若取值为INFINITE则造成无限延迟。

返回值:该函数没有返回值。

备注:一个线程可以在调用该函数时将睡眠时间设为0毫秒,以将剩余的时间片交出。使用Sleep 函数和直接或间接创建窗口的代码时必须非常小心。若线程创建了窗口,它就必须处理消息。消息广播被发送给系统中的所有窗口。若有一个线程调用Sleep函数时使用了无限延迟,则系统会死锁。两个直接创建窗口的代码的例子是DDE和COM CoInitialize。因此,若有一个创建窗口的线程,则使用MsgWaitForMutipleObjects和MsgWaitForMutipleObjectsEx函数,而不使用Sleep()函数。

4.2.4 GetTail()

函数功能:获取此列表中的头元素

函数原型: CTypedPtrList::GetTail

参数: 指定保存在列表中的元素类型的模板参数

返回值:如果是通过一个指向const CTypedPtrList的指针访问此列表,则GetTail返回一个类型由模板参数TYPE指定的指针。这使此函数只能被使用在赋值语句的右边,这样就保护了列表不被修改。

如果列表被直接访问,或通过一个指向CTypedPtrList的指针访问,则GetTail返回对一个类型由模板参数TYPE指定的指针的引用。这使得此函数可以使用在赋值语句的任何一边,从而允许该列表可以被修改

备注:在调用GetTail之前,你必须保证该列表不是空的。如果列表是空的,则Microsoft基础类库的调试版将给出断言。使用IsEmpty来检验列表是否包含元素。

4.3 消费者线程

原码:

UINT CMultiThreadDlg::Thread_consumer(LPVOID param)//消费者

{

while(threadController)

{

WaitForSingleObject(hMutex,INFINITE);//等待同步事件

if(number>0)//缓冲区有内容

{

srand ((unsigned)time(NULL));

int n;

n=rand()%4+1;

Sleep(n*1000);//延时1-5秒

CString string;

string.Format("%s%d%s%d","0xaa",strlen(list.GetHead()),list.GetHead(),sizeof(list.GetT ail())^sizeof(strlen(list.GetHead())));//得到缓冲区内容

pbox4->AddString(string);

list.RemoveHead();//删除头内容

number--;

ReleaseMutex(hMutex);//释放互斥

}

else//否则解除互斥

{

ReleaseMutex(hMutex);

}

}

return 0;

}

原码功能:执行消费费线程,当缓冲区有数据时取出内容,并且按随机时间(1-5秒的范围内)取数,按固定的格式消息头(1B,固定为0xaa),消息长度(1B),消息内容(nB),校验和(1B)做输出.

4.3.1 Sleep()

函数功能:该函数对于指定的时间间隔挂起当前的执行线程。

函数原型:void Sleep(DWORD dwMilliseconds);

参数:dwMilliseconds:定义挂起执行线程的时间,以毫秒为单位。取值为0时,该线程将余下的时间片交给处于就绪状态的同一优先级的其他线程。若没有处于就绪状态的同一优先级的其他线程,则函数立即返回,该线程继续执行。若取值为INFINITE则造成无限延迟。

返回值:该函数没有返回值。

备注:一个线程可以在调用该函数时将睡眠时间设为0毫秒,以将剩余的时间片交出。使用Sleep 函数和直接或间接创建窗口的代码时必须非常小心。若线程创建了窗口,它就必须处理消息。消息广播被发送给系统中的所有窗口。若有一个线程调用Sleep函数时使用了无限延迟,则系统

会死锁。两个直接创建窗口的代码的例子是DDE和COM CoInitialize。因此,若有一个创建窗口的线程,则使用MsgWaitForMutipleObjects和MsgWaitForMutipleObjectsEx函数,而不使用Sleep()函数。

4.3.2 ReleaseMutex()

函数功能:该函数放弃指定互斥对象的拥有权。

函数原型:BOOL ReleaseMutex(HANDLE hMutex);

参数:hMutex:互斥对象句柄。为CreateMutex或OpenMutex函数的返回值。

返回值:如果函数调用成功,那么返回值是非零值;如果函数调用失败,那么返回值是零值。若想获得更多错误信息,请调用GetLastError函数。

备注:如果调用线程不拥有互斥对象,ReleaseMutex函数失败。

一个线程通过调用等待函数拥有互斥对象。创建该互斥对象的线程也拥有互斥对象,而不需要调用等待函数。当互斥对象的所有者线程不再需要互斥对象时,它可以调用ReleaseMutex 函数。

当一个线程拥有一个互斥对象后,它可以用该互斥对象多次调用等待函数而不会阻塞。这防止一个线程等待一个它已拥有的互斥对象时出现死锁。不过,为了释放所有权,该线程必须为每一个等待操作调用一次ReleaseMutex函数。

4.3.3 WaitForSingleObject

函数功能:当如下情况之一发生时该函数返回:指定对象处于信号态;超时。

函数原型:

DWORD WaitForSingleObject( HANDLE hHandle,DWORD dwMilliseconds);

参数:hHandle:等待对象句柄。若想了解指定句柄的对象类型列表,参阅下面说明部分。

在WndowsNT中,句柄必须有SYNCHRONIZE访问权限。若想获得更多的信息,请查看Standard Access Rights。

dwMilliseconds:指定以毫秒为单位的超时间隔。如果超时,即使对象的状态是非信号态的并且没有完成,函数也返回。如果dwMilliseconds是0,函数测试对象的状态并立刻返回:如果dwMillseconds是INFINlTE,函数从不超时。

返回值:如果函数调用成功,返回值表明引起函数返回的事件。可能值如下:

WAIT_ABANDONED:指定对象是互斥对象,在线程被终止前,线程没有释放互斥对象。互斥对象的所属关系被授予调用线程,并且该互斥对象被置为非信号态。

WAIT_OBJECT_0:指定对象的状态被置为信号态。

WAlT_TIMEOUT:超时,并且对象的状态为非信号态。

如果函数调用失败,返回值是WAIT_FAILED。若想获得更多错误信息,请调用GetLastError函

数。

4.3.4 GetHead

函数功能: 用来获取代表此列表中的头元素的指针

函数原型: CTypedPtrList::GetHead

参数: 指定保存在列表中的元素类型的模板参数

返回值: 如果是通过一个指向const CTypedPtrList的指针访问此列表,则GetHead返回一个类型由模板参数TYPE指定的指针。这使此函数只能被使用在赋值语句的右边,这样就保护了列表不被修改。

如果列表被直接访问,或通过一个指向CTypedPtrList的指针访问,则GetHead返回对一个类型由模板参数TYPE指定的指针的引用。这使得此函数可以使用在赋值语句的任何一边,从而允许该列表可以被修改

四.程序主要源码清单

由于代码比较多,在此我给出生产者—消费者中核心部分调试好,可以运行的源码,如果要完整代码,请查看本程序原整的原代码.

// MultiThreadDlg.cpp : implementation file

#include "stdafx.h"

#include "MultiThread.h"

#include "MultiThreadDlg.h"

#include "afxtempl.h"

#include "afxmt.h"

#include "time.h"

#include "stdio.h"

#include "stdlib.h"

#include "winbase.h"

volatile int threadController;

volatile int check;

volatile HANDLE hMutex;

CList list;

CListBox *pbox1,*pbox2,*pbox3,*pbox4,*pbox5;

int number;

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

//class CObject;

// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog//初始化消息句柄

{

public:

CAboutDlg();

enum { IDD = IDD_ABOUTBOX };

protected:

virtual void DoDataExchange(CDataExchange* pDX); // Implementation protected:

DECLARE_MESSAGE_MAP()

};

void CAboutDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)

END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////// CMultiThreadDlg::CMultiThreadDlg(CWnd* pParent /*=NULL*/) : CDialog(CMultiThreadDlg::IDD, pParent)

{ m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

}

void CMultiThreadDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

}

BEGIN_MESSAGE_MAP(CMultiThreadDlg, CDialog)

ON_WM_SYSCOMMAND()

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_START, OnStart)

ON_BN_CLICKED(IDC_STOP, OnStop)

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

BOOL CMultiThreadDlg::OnInitDialog()//创建线程

{

CDialog::OnInitDialog();

ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);

ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);

if (pSysMenu != NULL)

{

CString strAboutMenu;

strAboutMenu.LoadString(IDS_ABOUTBOX);

if (!strAboutMenu.IsEmpty())

{

pSysMenu->AppendMenu(MF_SEPARATOR);

pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);

}

}

// 获取对话框中子窗口控件的句柄

pbox1=(CListBox*)GetDlgItem(IDC_LIST1);

pbox2=(CListBox*)GetDlgItem(IDC_LIST2);

pbox3=(CListBox*)GetDlgItem(IDC_LIST3);

pbox4=(CListBox*)GetDlgItem(IDC_LIST4);

pbox5=(CListBox*)GetDlgItem(IDC_LIST5);

number=0;

return TRUE; // return TRUE交出控制权

void CMultiThreadDlg::OnSysCommand(UINT nID, LPARAM lParam)//获取控制命令{

if ((nID & 0xFFF0) == IDM_ABOUTBOX)

{

CAboutDlg dlgAbout;

dlgAbout.DoModal();

}

else

{

CDialog::OnSysCommand(nID, lParam);

}

}

//窗体显示大小

void CMultiThreadDlg::OnPaint()

{

if (IsIconic())

{

CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

int cxIcon = GetSystemMetrics(SM_CXICON);

int cyIcon = GetSystemMetrics(SM_CYICON);

CRect rect;

GetClientRect(&rect);

int x = (rect.Width() - cxIcon + 1) / 2;

int y = (rect.Height() - cyIcon + 1) / 2;

dc.DrawIcon(x, y, m_hIcon);

}

else

{

CDialog::OnPaint();

}

}

HCURSOR CMultiThreadDlg::OnQueryDragIcon()

return (HCURSOR) m_hIcon;

}

void CMultiThreadDlg::OnStart() //开始创建线程

{

hMutex=CreateMutex(NULL,FALSE,NULL);//初始化

threadController=1;

check=TRUE;

HWND hWnd=GetSafeHwnd();//得到控制权

AfxBeginThread(ThreadProc,hWnd,THREAD_PRIORITY_NORMAL);//启用生产者线程1(P1) AfxBeginThread(ThreadProc2,hWnd,THREAD_PRIORITY_NORMAL);//启用生产者线程

2(P2)

AfxBeginThread(ThreadProc3,hWnd,THREAD_PRIORITY_NORMAL);//启用生产者线程

3(P3)

AfxBeginThread(Thread_consumer,hWnd,THREAD_PRIORITY_NORMAL);//启用消费者线程1(S1)

AfxBeginThread(Thread_consumer2,hWnd,THREAD_PRIORITY_NORMAL);//启用消费者线程2(S2)

}

void CMultiThreadDlg::OnStop()//双击暂时停止所有工作

{

threadController=0;

check=FALSE;

}

UINT CMultiThreadDlg::ThreadProc(LPVOID param)//生产者1的生产过程

{

while(threadController)

{

WaitForSingleObject(hMutex,INFINITE);//等待一个同步事件的到来

if(number<20)

{

srand ((unsigned)time(NULL));//返回一个随机数

int n;

n=rand()%9+1;//随机显示字数

Sleep(n*100);

CString str;

CString str_;

for(int i=0; i

{

str_.Format("%c",65+rand()%9+1);//显示大写字母

str.Insert(i,str_);//插入成一列

}

list.AddTail(CString(str));

CString string;

string.Format("%s%d%s%d","0xaa",n,list.GetTail(),sizeof(list.GetTail())^sizeof(n));//按格式消息头(1B,固定为0xaa),消息长度(1B),消息内容(nB),校验和(1B)

pbox1->AddString(string);//打印

number++;

}

ReleaseMutex(hMutex);//释放互斥

}

return 0;

}

UINT CMultiThreadDlg::ThreadProc2(LPVOID param)

{

while(threadController)

{

WaitForSingleObject(hMutex,INFINITE);

if(number<20)

{

srand ((unsigned)time(NULL));

int n;

n=rand()%9+1;

Sleep(n*100);

CString str;

CString str_;

for(int i=0; i

{

str_.Format("%c",97+rand()%9+1);

str.Insert(i,str_);

}

list.AddTail(CString(str));

CString string;

string.Format("%s%d%s%d","0xaa",n,list.GetTail(),sizeof(list.GetTail())^sizeof(n));

pbox2->AddString(string);

number++;

}

ReleaseMutex(hMutex);

}

return 0;

}

UINT CMultiThreadDlg::ThreadProc3(LPVOID param)

{

while(threadController)

{

WaitForSingleObject(hMutex,INFINITE);//等待一个同步事件的到来

if(number<20)

{

srand ((unsigned)time(NULL));//返回一个随机数

int n;

n=rand()%9+1;

Sleep(n*100);

CString str;

CString str_;

for(int i=0; i

{

str_.Format("%c",48+rand()%9+1);

str.Insert(i,str_);

}

list.AddTail(CString(str));

CString string;

string.Format("%s%d%s%d","0xaa",n,list.GetTail(),sizeof(list.GetTail())^sizeof(n));

//list.AddHead(CString(string));

pbox3->AddString(string);

number++;

}

ReleaseMutex(hMutex);

}

return 0;

}

UINT CMultiThreadDlg::Thread_consumer(LPVOID param)//消费者

{

while(threadController)

{

WaitForSingleObject(hMutex,INFINITE);//等待同步事件

if(number>0)//缓冲区有内容

{

srand ((unsigned)time(NULL));

int n;

n=rand()%4+1;

Sleep(n*1000);//延时1-5秒

CString string;

string.Format("%s%d%s%d","0xaa",strlen(list.GetHead()),list.GetHead(),sizeof(list.GetTail())^siz eof(strlen(list.GetHead())));//得到缓冲区内容

pbox4->AddString(string);

list.RemoveHead();//删除头内容

4:一个经典的多线程同步问题汇总

一个经典的多线程同步问题 程序描述: 主线程启动10个子线程并将表示子线程序号的变量地址作为参数传递给子线程。子线程接收参数 -> sleep(50) -> 全局变量++ -> sleep(0) -> 输出参数和全局变量。 要求: 1.子线程输出的线程序号不能重复。 2.全局变量的输出必须递增。 下面画了个简单的示意图: 分析下这个问题的考察点,主要考察点有二个: 1.主线程创建子线程并传入一个指向变量地址的指针作参数,由于线程启动须要花费一定的时间,所以在子线程根据这个指针访问并保存数据前,主线程应等待子线程保存完毕后才能改动该参数并启动下一个线程。这涉及到主线程与子线程之间的同步。 2.子线程之间会互斥的改动和输出全局变量。要求全局变量的输出必须递增。这涉及到各子线程间的互斥。 下面列出这个程序的基本框架,可以在此代码基础上进行修改和验证。 //经典线程同步互斥问题 #include #include #include long g_nNum; //全局资源 unsigned int__stdcall Fun(void *pPM); //线程函数 const int THREAD_NUM = 10; //子线程个数 int main() { g_nNum = 0;

HANDLE handle[THREAD_NUM]; int i = 0; while (i < THREAD_NUM) { handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL); i++;//等子线程接收到参数时主线程可能改变了这个i的值} //保证子线程已全部运行结束 WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE); return 0; } unsigned int__stdcall Fun(void *pPM) { //由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来int nThreadNum = *(int *)pPM; //子线程获取参数 Sleep(50);//some work should to do g_nNum++; //处理全局资源 Sleep(0);//some work should to do printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum); return 0; } 运行结果:

进程同步机制与互斥-生产者消费者问题

学习中心: 专业: 年级:年春/秋季 学号: 学生: 题目:进程同步与互斥生产者-消费者问题 1.谈谈你对本课程学习过程中的心得体会与建议? 转眼间,学习了一个学期的计算机操作系统课程即将结束。在这个学期中,通过老师的悉心教导,让我深切地体会到了计算机操作系统的一些原理和具体操作过程。在学习操作系统之前,我只是很肤浅地认为操作系统只是单纯地讲一些关于计算机方面的操作应用,并不了解其中的具体操作过程 1.1设计思路 在这次设计中定义的多个缓冲区不是环形循环的,并且不需要按序访问。其中生产者可以把产品放到某一个空缓冲区中,消费者只能消费被指定生产者生产的产品。本设计在测试用例文件中指定了所有生产和消费的需求,并规定当共享缓冲区的数据满足了所有有关它的消费需求后,此共享才可以作为空闲空间允许新的生产者使用。

本设计在为生产者分配缓冲区时各生产者之间必须互斥,此后各个生产者的具体生产活动可以并发。而消费者之间只有在对同一个产品进行消费时才需要互斥,它们在消费过程结束时需要判断该消费者对象是否已经消费完毕并释放缓冲区的空间。 1.2程序流程图 1.3基本内容 在设计程序时主要有三个主体部分、三个辅助函数和一个数据结构。 其中主体部分为一个主函数main(),用于初始化缓冲区和各个同步对象,并完成线程信息的读入,最后根据该组的线程记录启动模拟线程,并等待所有线程的运 Y

行结束后退出程序; 生产者函数Produce()和消费者函数Consume(),生产者和消费者函数运行于线程中完成对缓冲区的读、写动作,根据此处生产消费的模型的特点,生产者和消费者之间通过使用同步对象实现了生产和消费的同步与互斥,是本实验的核心所在。 另外三个辅助性函数被生产者和消费者函数调用,是上述生产和消费函数中对缓冲区进行的一系列处理。 3)在实现本程序的消费生产模型时,具体的通过如下同步对象实现互斥: ①设一个互斥量h_mutex,以实现生产者在查询和保留缓冲区内的下一个位置时进行互斥。 ②每一个生产者用一个信号量与其消费者同步,通过设置h_Semaphore[MAX_THREAD_NUM]信号量 ③数组实现,该组信号量用于相应的产品已产生。同时用一个表示空缓冲区

用信号量实现线程同步与互斥

用信号量实现线程同步与互斥 一、相关Win32 API函数 1、创建线程 HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId); 函数作用:在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄。 各参数含义: ?lpThreadAttributes:指向一个SECURITY_ATTRIBUTES 结构的指针,该结构决定了线程的安全属性,一般置为NULL; ?dwStackSize:指定了线程的堆栈深度,一般都设置为0; ?lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。 一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是线程函数 名;函数名称没有限制,但是必须以下列形式声明:DWORD WINAPI ThreadProc (PVOID pParam) ; ?lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数; ?dwCreationFlags:控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产 生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用; ?lpThreadId:该参数返回所创建线程的ID。 如果创建成功则返回线程的句柄,否则返回NULL。 例如: for (int i=0;i

同步和互斥实验-吃水果问题、消费者问题

实验3 报告 源程序1 /** 作者:wwj 时间:2012/4/12 功能:实现吃水果问题 **题目内容:桌子有一只盘子,只允许放一个水果,父亲专向盘子放苹果,母亲专向盘子放桔子儿子专等吃盘子的桔子,女儿专等吃盘子的苹果。只要盘子为空,父亲或母亲就可以向盘子放水果,仅当盘子有自己需要的水果时,儿子和女儿可从盘子取出。请给出四个人之间的同步关系,并用pv操作实现四个人的正确活动的问题。** **题目分析:父亲和女儿是相互制约的,父亲进程执行完即往盘中放入苹果后,女儿进程才能执行即吃苹果,是同步关系; 母亲和儿子是相互制约的,母亲进程执行完即往盘中放入桔子,儿子进程才能执行即吃桔子,也是同步关系 而父亲和母亲这两个进程不能同时进行,是互斥关系;** **/ #include #include using namespace std; //声明句柄 HANDLE EmptyPlate; HANDLE Apple; HANDLE orange; HANDLE fatherThread; HANDLE motherThread; HANDLE sonThread; HANDLE daughterThread; //线程函数声明 DWORD WINAPI father(LPVOID IpParameter); DWORD WINAPI mother(LPVOID IpParameter); DWORD WINAPI daughter(LPVOID IpParameter); DWORD WINAPI son(LPVOID IpParameter); int main() { //创建信号量 EmptyPlate = CreateSemaphore(NULL,1,1,NULL); //盘子 Apple = CreateSemaphore(NULL,0,1,NULL); //苹果

操作系统 实验 五 线程间的互斥与同步

实验五线程间的互斥与同步 实验学时:2学时 实验类型:验证、设计型 一、实验目的 理解POSIX线程(Pthread)互斥锁和POSIX信号量机制,学习它们的使用方法;编写程序,实现多个POSIX线程的同步控制。 二,实验内容 创建4个POSIX线程。其中2个线程(A和B)分别从2个数据文件(data1.txt和data2.txt)读取10个整数. 线程A和B把从文件中读取的逐一整数放入一个缓冲池. 缓冲池由n个缓冲区构成(n=5,并可以方便地调整为其他值),每个缓冲区可以存放一个整数。另外2个线程,C和D,各从缓冲池读取10数据。线程C、D每读出2个数据,分别求出它们的和或乘积,并打印输出。 提示:在创建4个线程当中,A和B是生产者,负责从文件读取数据到公共的缓冲区,C和D是消费者,从缓冲区读取数据然后作不同的计算(加和乘运算)。使用互斥锁和信号量控制这些线程的同步。不限制线程C和D从缓冲区得到的数据来自哪个文件。 在开始设计和实现之前,务必认真阅读课本6.8.4节和第6章后面的编程项目——生产者-消费者问题。

三,实验要求 按照要求编写程序,放在相应的目录中,编译成功后执行,并按照要求分析执行结果,并写出实验报告。 四,实验设计 1,功能设计 根据实验要求,主程序需要创建四个线程,两个线程负责从文件读取数据到缓冲区,两个线程负责将缓冲区的数据做数学运算。由于同一个进程中的各个线程共享资源,可以用一个二维数组的全局变量作为公共缓冲区,同时还需要一个整形全局变量size用来做数组的索引。读线程的运行函数打开不同的文件并从中读取数据到二维数组中,每次写入数组后size加一。运算线程从二维数组中读数并做运算,每次读数之前size减一。本题的关键在于如何使用信号量保证进程的同步与互斥。在运算线程从缓冲区读取之前缓冲区里必须有数,即任意时刻运算操作的执行次数必须小于等于读取操作的执行次数。同时应该保证两个读线程和两个运算线程两两互斥。由于以上分析,使用了四个信号量sem1,sem2,sem3和sem4。sem1保证线程1和线程2互斥,sem2保证线程3和线程4互斥,sem3保证线程3和线程4互斥,sem4保证线程4和线程1互斥。即这四个信号量使四个线程循环进行,从而保证了运行结果的正确性。 源代码及注释: #include #include #include #define NUM 200

操作系统实验-进程同步与互斥

实验四:进程的管道通信 实验题目 进程的管道通信 实验目的 加深对进程概念的理解,明确进程和程序的区别。学习进程创建的过程,进一步认识进程并发执行的实质。分析进程争用资源的现象,学习解决进程互斥的方法。学习解决进程同步的方法。掌握Linux系统中进程间通过管道通信的具体实现 实验内容 使用系统调用pipe()建立一条管道,系统调用fork()分别创建两个子进程,它们分别向管道写一句话,如: Child process1 is sending a message! Child process2 is sending a message! 父进程分别从管道读出来自两个子进程的信息,显示在屏幕上。 当然,仅仅通过屏幕上输出这两句话还不能说明实现了进程的管道通信,为了能够更好的证明和显示出进程的同步互斥和通信,在其中要加入必要的跟踪条件,如一定的输出语句等,来反映程序的并发执行情况 实验要求 这是一个设计型实验,要求自行、独立编制程序。两个子进程要并发执行。实现管道的互斥使用。当一个子进程正在对管道进行写操

作时,另一个欲写入管道的子进程必须等待。使用系统调用lockf(fd[1],1,0)实现对管道的加锁操作,用lockf(fd[1],0,0)解除对管道的锁定。实现父子进程的同步,当父进程试图从一空管道中读取数据时,便进入等待状态,直到子进程将数据写入管道返回后,才将其唤醒。 为了清楚的反应进程的同步,在子进程完成相应的操作后,调用sleep()函数睡眠一段时间(程序中定为3s)。父进程先执行wait()函数,当有子进程执行完毕后,会得到子进程的返回结果并清理子进程。若子进程没执行完,父进程一直执行wait()进行监听,知道有一个子进程执行完成为僵尸进程。 程序中用到的系统调用 因为程序时在linux系统上进行编写的,所以其中要利用到相关的linux提供的系统调用。 所用到的系统调用包含在如下头文件中。 #include #include #include #include #include #include fork() 用于创一个子进程。 格式:int fork();

四种进程或线程同步互斥的控制方法

四种进程或线程同步互斥的控制方法 1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。 2、互斥量:为协调共同对一个共享资源的单独访问而设计的。 3、信号量:为控制一个具有有限数量用户资源而设计。 4、事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。 一临界区 临界区的使用在线程同步中应该算是比较简单,说它简单还是说它同后面讲到的其它方法相比更容易理解。举个简单的例子:比如说有一个全局变量(公共资源)两个线程都会对它进行写操作和读操作,如果我们在这里不加以控制,会产生意想不到的结果。假设线程A 正在把全局变量加1然后打印在屏幕上,但是这时切换到线程B,线程B又把全局变量加1然后又切换到线程A,这时候线程A打印的结果就不是程序想要的结果,也就产生了错误。解决的办法就是设置一个区域,让线程A在操纵全局变量的时候进行加锁,线程B如果想操纵这个全局变量就要等待线程A释放这个锁,这个也就是临界区的概念。 二互斥体 windows api中提供了一个互斥体,功能上要比临界区强大。也许你要问,这个东东和临界区有什么区别,为什么强大?它们有以下几点不一致: 1.critical section是局部对象,而mutex是核心对象。因此像waitforsingleobject是不可以等待临界区的。 2.critical section是快速高效的,而mutex同其相比要慢很多 3.critical section使用围是单一进程中的各个线程,而mutex由于可以有一个名字,因此它是可以应用于不同的进程,当然也可以应用于同一个进程中的不同线程。 4.critical section 无法检测到是否被某一个线程释放,而mutex在某一个线程结束之后会产生一个abandoned的信息。同时mutex只能被拥有它的线程释放。下面举两个应用mutex 的例子,一个是程序只能运行一个实例,也就是说同一个程序如果已经运行了,就不能再运行了;另一个是关于非常经典的哲学家吃饭问题的例子。 三事件 事件对象的特点是它可以应用在重叠I/O(overlapped I/0)上,比如说socket编程中有两种模型,一种是重叠I/0,一种是完成端口都是可以使用事件同步。它也是核心对象,因此可以被waitforsingleobje这些函数等待;事件可以有名字,因此可以被其他进程开启。 四信号量 semaphore的概念理解起来可能要比mutex还难,我先简单说一下创建信号量的函数,因为我在开始使用的时候没有很快弄清楚,可能现在还有理解不对的地方,如果有错误还是请大侠多多指教。 CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD LONG lInitialCount, // initial count LONG lMaximumCount, // maximum count LPCTSTR lpName // object name )

进程同步与互斥汇总

进程同步与互斥

进程的PV操作 在操作系统中,P、V操作是进程管理中的难点。这是1968年荷兰人Dijkstra 给出的一种解决并发进程间互斥和同步关系的通用方法。 1. P、V操作的意义 定义了信号量及其上的P操作和V操作,来实现并发进程间的同步和互斥,甚至可以用来管理资源的分配。P、V操作因交换的信息量少,属于进程的低级通信。 2. 什么是信号量? 信号量(semaphore)是由一个值和一个指针构成的数据结构。值为整型变

量,表示信息量的值;指针指向进程控制块(PCB)队列的队头,表示等待该信号量的下一个进程。如下图所示。 信号量的一般结构及PCB队列 信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。注意,信号量的初值不能为负,且其值只能由P、V操作来改变。 3. P、V操作的含义 P、V操作由P操作原语和V操作原语组成(原语是不可中断的过程),对信号量S进行操作,具体定义如下: P(S): ①将信号量S的值减1,即S=S-1; ②如果S≥0,则该进程继续执行;否则该进程状态置为阻塞状态,进程PCB 排入信号量PCB队列末尾,放弃CPU,等待V操作的执行。 V(S): ①将信号量S的值加1,即S=S+1; ②如果S≤0,释放信号量队列中第一个PCB所对应的进程,将进程状态由阻塞态改为就绪态。执行V操作的进程继续执行。 一般来说,信号量S≥0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个单位资源,因此S的值减1;当S<0时,表示已经没有可用资源,请求者必须等待别的进程释放该类资源,它才能运行下去。而执行一个V操作意味着释放一个单位资源,因此S的值加1;若S≤0,表示有某些进程正在等待该资源,因此要唤醒一个阻塞状态的进程,使之成为就绪状态。 4. 利用信号量和P、V操作实现进程互斥 一般地,n个进程利用信号量和P、V操作实现进程互斥的一般模型如下: 进程P 1进程P 2 ……进程Pn …… …… …… P(S); P(S); P(S); 临界区;临界区;临界区; V(S); V(S); V(S); …… …… …… …… 其中S是互斥信号量,初值为1。 使用P、V操作实现进程互斥时应该注意的问题是: (1)每个程序中,用户实现互斥的P、V操作必须成对出现,先做P操作,进临界区,后做V操作,出临界区。若有多个分支,要认真检查P、V操作的成对性。 (2)P、V操作应分别紧靠临界区的头尾部,临界区的代码应尽可能短,不能有死循环。 (3)互斥信号量的初值一般为1。 由于用于互斥的信号量sem与所有的并发进程有关,所以称之为公有信号量。公有信号量的值反映了公有资源的数量。只要把临界区置于P(sem)和V(sem)

进程(线程)的同步与互斥实验报告

操作系统实验报告 课程名称操作系统实验名称进程(线程)的同步与互斥成绩 学生姓名作业君专业软件工程班级、学号 同组者姓名无实验日期2020 一、实验题目:进程(线程)的同步与互斥 二、实验目的: 自行编制模拟程序,通过形象化的状态显示,加深理解进程的概念、进程之间的状态转换及其所带来的PCB内容、组织的变化,理解进程与其PCB间的一一对应关系。1.掌握基本的同步与互斥算法,理解生产者消费者模型。 2.学习使用Windows中基本的同步对象,掌握相关API的使用方法。 3.了解Windows中多线程的并发执行机制,实现进程的同步与互斥 三、实验内容与要求: 1.实验内容 以生产者/消费者模型为依据,在Windows 环境下创建一个控制台进程,在该进程中创建n个线程模拟生产者和消费者,实现进程(线程)的同步与互斥。 2.实验要求 学习并理解生产者/消费者模型及其同步/互斥规则; 学习了解Windows同步对象及其特性; 熟悉实验环境,掌握相关API的使用方法; 设计程序,实现生产者/消费者进程(线程)的同步与互斥; 四、算法描述(含数据结构定义)或流程图 #include #include #include #include #include #include using namespace std;

#define MAX_THREAD_NUM 64 //最大线程数 #define INTE_PER_SEC 1000 //延迟时间的毫秒值 const int SIZE_OF_BUFFER = 10; //缓冲区长度 int ProductID = 0; //产品号 int ConsumeID = 0; //将被消耗的产品号 int in = 0; //产品进缓冲区时的缓冲区下标 int out = 0; //产品出缓冲区时的缓冲区下标 bool running = true; //判断程序能否继续执行的逻辑值 int g_buffer[SIZE_OF_BUFFER]; //缓冲区是个循环队列 HANDLE g_hMutex; //公有信号量,用于线程间的互斥HANDLE g_hFullSemaphore; //生产者的私有信号量,当缓冲区满时迫使生产者等待 HANDLE g_hEmptySemaphore; //消费者的私有信号量,当缓冲区空时迫使消费者等待 //定义一个结构体用于存储线程的信息 struct ThreadInfo { int serial; //线程号 char entity; //线程类别(生产者或消费者) double delay; //等待时间 double persist; //操作时间 }; //生产者 void Producer(void* p) { //定义变量用于存储当前线程的信息 DWORD m_delay; DWORD m_persist; int m_serial; //从参数中获得信息 m_serial = ((ThreadInfo*)(p))->serial; m_delay = (DWORD)(((ThreadInfo*)(p))->delay * INTE_PER_SEC); m_persist = (DWORD)(((ThreadInfo*)(p))->persist * INTE_PER_SEC); while (running) { //P操作 cout << "生产者线程 " << m_serial << " 请求生产." << endl; WaitForSingleObject(g_hEmptySemaphore, INFINITE);

同步与互斥

习题解答: ?何谓与时间有关的错误? 举例说明之。 答:并发进程的执行实际上是进程活动的某种交叉,某些交叉次序可能得到错误结果。由于具体交叉的形成与进程的推进速度有关,而速度是时间的函数,因而将这种错误称为与时间有关的错误。 例如,两个并发进程的程序如下: int n=0; main( ){ 创建进程A; 创建进程B; }; A( ){ while(1){ n++; } }; B( ){ while(1){ 睡眠一段时间;printf(“%d”,n); n=0; } }; 假设进程A被部署在公园入口的终端上,用来记录一段时间内进入公园的人数,进程B被部署在公园的控制中心,用来输出一段时间内进入公园的总人数。进程A和进程B共享全局变量n,n表示记录下的人数。如果在进程B执行完打印语句后被进程A打断,进程A执行了若干次变量自增语句,之后进程B接着执行清0语句,那么进程A对n的累加丢失了,相当于进程B被打断的这段时间内进入公园的人没有被记录下来。发生与时间有关的错误。 ?有人说,假设两个进程之间没有共享内存,则二者之间没有公共变量,这种说法准确吗? 说明原因。 答:如果只从用户空间考虑,这种说法是正确的。但从操作系统的角度来说并不准确。两个没有公共内存的用户进程可能同时(宏观)进入操作系统,并访问操作系统空间中的公共变

量。 ?何谓忙式等待? 是否还有其它方式的等待? 比较它们之间的联系和差别。 答:不进入等待状态的等待称为忙式等待。另一种等待方式是阻塞式等待,进程得不到共享资源时将进入阻塞状态,让出CPU给其他进程使用。忙等待和阻塞式等待的相同之处在于进程都不具备继续向前推进的条件,不同之处在于处于忙等待的进程不主动放弃CPU,尽管CPU 可能被剥夺,因而是低效的;而处于阻塞状态的进程主动放弃CPU,因而是高效的。 ?下列进程互斥方法哪些存在忙式等待问题? (1)软件: 面包店算法(2) 硬件: TS指令(3) 关中断指令 答:(1)、(2)存在忙等待问题。 ?为何开关中断进程互斥方法仅在单CPU系统中是有效的? 答:关中断方法不适用于多CPU系统,因为关中断只能保证CPU不由一个进程切换到另外一个进程,从而防止多个进程并发地进入公共临界区域。但即使关中断后,不同进程仍可以在不同CPU上并行执行关于同一组共享变量的临界区代码. ?在多处理机系统中,软件互斥方法是否有效?为什么? 答:依然有效。多处理机并行与单处理并发之间的差别在于程序交叉的粒度,单处理机机环境中进程交叉发生在指令之间,多处理机环境中进程交叉发生在指令周期之间。由于纯软件互斥算法并不依赖特殊的硬件指令(如test_and_set),指令之间的交叉与指令周期之间的交叉结果相同。 ?试分析临界区域的大小与系统并发性之间的关系。 答:关于同一组变量的临界区域是不能并发执行的代码,临界区越大,并发性越差,因而编写并发程序应尽量缩小临界区域范围。 ?设CR1是关于一组共享变量SV1的临界区域,CR2是关于另外一组共享变量SV2的临

实验1编程实现进程(线程)同步和互斥

实验1编程实现进程(线程)同步和互斥 一、实验目的 ①通过编写程序实现进程同步和互斥,使学生掌握有关进程(线程)同步与 互斥的原理,以及解决进程(线程)同步和互斥的算法,从而进一步巩固进程(线程)同步和互斥 ②等有关的内容。 ③了解Windows2000/XP中多线程的并发执行机制,线程间的同步和互斥。 ④学习使用Windows2000/XP中基本的同步对象,掌握相应的 ⑤API函数。 ⑥掌握进程和线程的概念,进程(线程)的控制原语或系统调用的使用。 ⑦掌握多道程序设计的基本理论、方法和技术,培养学生多道程序设计的能 力。 二、实验内容 在Windows XP、Windows 2000等操作系统下,使用的VC、VB、java或C 等编程语言,采用进程(线程)同步和互斥的技术编写程序实现生产者消费者问题或哲学家进餐问题或读者-写者问题或自己设计一个简单进程(线程)同步和互斥的实际问题。 三、实验要求 ①经调试后程序能够正常运行。 ②采用多进程或多线程方式运行,体现了进程(线程)同步和互斥的关系。 ③程序界面美观。 四、实验步骤、过程 让写者与读者、读者与读者之间互斥的访问同一数据集,在无写者进程到来时各读者可同时的访问数据集,在读者和写者同时等待时写者优先唤醒。设置两个全局变量readcount 和writecount来记录读者与写者的数目,设置了3个信号量。h_mutex1表示互斥对象对阻塞在read这一个过程实现互斥,h_mutex2实现全局变量readcount操作上的互斥,h_mutex3实现对全局变量writecount的互斥访问。设置了两个临界区,为了实现写者优先,用了临界区read。数据结构:(1)用了两个临界区(2)自定义结构ThreadInfo记录一条线程信息,多个线程对应一个ThreadInfo数组。(3)设置了互斥量h_mutex1,实现了互斥对象对阻

同步和互斥的区别_

同步和互斥的区别

信号量互斥量条件变量 同步和互斥 一、信号量:用于进程间通信(linux中用于线程) 独立于进程在两个进程中都要初始化,若一个已创建,则另一个获取可以执行多次v操作,然后再执行p操作 信号量的一些概念: 以下是信号灯(量)的一些概念: 信号灯与互斥锁和条件变量的主要不同在于”灯”的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于”等待”操作,即资源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持灯亮状态。当然,这样的操作原语也意味着更多的开销。 信号灯的应用除了灯亮/灯灭这种二元灯以外,也可以采用大于1的灯数,以表示资源数大于1,这时可以称之为多元灯。 二、互斥量:(用于在线程间的通信) 1、在一个进程中创建 三、信号量与互斥量的区别 1. 互斥量用于线程的互斥,信号量用于线程的同步。 这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源 以上区别是主要想记住的。 note:信号量可以用来实现互斥量的功能 2. 互斥量值只能为0/1,信号量值可以为非负整数。 也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。3、互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。 4、作用域 信号量: 进程间或线程间(linux仅线程间) 互斥锁: 线程间 5、上锁时 信号量: 只要信号量的value大于0,其他线程就可以sem_wait(p操作-1)成功,成功后信号量的value减一。若value值不大于0,则

多线程同步方法及比较

多线程同步方法及比较 多线程同步方法: 1.临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数 据访问。. 2.互斥量:为协调一起对一个共享资源的单独访问而设计的。. 3.信号量:为控制一个具备有限数量用户资源而设计。. 4.事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。 临界区(Critical Section).. 确保在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。假如有多个线程试图同时访问临界区,那么在有一个线程进入后其他任何试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程能够继续抢占,并以此达到用原子方式操作共享资源的目的。 临界区包含两个操作原语: EnterCriticalSection()进入临界区 LeaveCriticalSection()离开临界区。 EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保和之匹配的LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。 MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是很简单的。只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。Lock()后代码用到的资源自动被视为临界区内的资源被保护。UnLock后别的线程才能访问这些资源。. ------------------------------------------------

Windows下进程同步与互斥

实验进程同步与互斥 一、实验目的 1.掌握基本的同步与互斥算法,理解生产者消费者模型。 2.学习使用Windows 2000/XP中基本的同步对象,掌握相关API的使用方法。 3.了解Windows 2000/XP中多线程的并发执行机制,实现进程的同步与互斥。 二、实验内容及要求 1.实验内容 以生产者/消费者模型为依据,在Windows 2000环境下创建一个控制台进程,在该进程中创建n个线程模拟生产者和消费者,实现进程(线程)的同步与互斥。 2.实验要求 ●学习并理解生产者/消费者模型及其同步/互斥规则; ●学习了解Windows同步对象及其特性; ●熟悉实验环境,掌握相关API的使用方法; ●设计程序,实现生产者/消费者进程(线程)的同步与互斥; ●提交实验报告。 三、相关知识介绍 1.同步对象 同步对象是指Windows中用于实现同步与互斥的实体,包括信号量(Semaphore)、互斥量(Mutex)、临界区(Critical Section)和事件(Events)等。本实验中使用到信号量、互斥量和临界区三个同步对象。 同步对象的使用步骤: ●创建/初始化同步对象。 ●请求同步对象,进入临界区(互斥量上锁)。 ●释放同步对象(互斥量解锁)。 这些对象在一个线程中创建,在其他线程中都可以使用,实现同步与互斥。 2.相关API的功能及使用 我们利用Windows SDK提供的API编程实现实验题目要求,而VC中包含有Windows SDK的所有工具和定义。要使用这些API,需要包含堆这些函数进行说明的SDK头文件——最常见的是Windows.h(特殊的API调用还需要包含其他头文件)。 下面给出的是本实验使用到的API的功能和使用方法简单介绍。 (1) CreateThread ●功能——创建一个在调用进程的地址空间中执行的线程 ●格式 HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress,

Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)

介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程,每条线程并行执行不同的任务。 线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O等会产生堵塞的情况的表现性能。在Unix系统中,一个进程包含很多东西,包括可执行程序以及一大堆的诸如文件描述符地址空间等资源。在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就需要在用户空间和内核空间进行频繁的切换,开销很大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得非常高效。 Hello World(线程创建、结束、等待) 创建线程 pthread_create 线程创建函数包含四个变量,分别为: 1. 一个线程变量名,被创建线程的标识 2. 线程的属性指针,缺省为NULL即可 3. 被创建线程的程序代码 4. 程序代码的参数 For example: - pthread_t thrd1? - pthread_attr_t attr? - void thread_function(void argument)? - char *some_argument? pthread_create(&thrd1, NULL, (void *)&thread_function, (void *) &some_argument); 结束线程 pthread_exit 线程结束调用实例:pthread_exit(void *retval); //retval用于存放线程结束的退出状态 线程等待 pthread_join pthread_create调用成功以后,新线程和老线程谁先执行,谁后执行用户是不知道的,这一块取决与操作系统对线程的调度,如果我们需要等待指定线程结束,需要使用pthread_join函数,这个函数实际上类似与多进程编程中的waitpid。 举个例子,以下假设 A 线程调用 pthread_join 试图去操作B线程,该函数将A线程阻塞,直到B线程退出,当B线程退出以后,A线程会收集B线程的返回码。 该函数包含两个参数:pthread_t th //th是要等待结束的线程的标识 void **thread_return //指针thread_return指向的位置存放的是终止线程的返回状态。 调用实例:pthread_join(thrd1, NULL); example1: 1 /************************************************************************* 2 > F i l e N a m e: t h r e a d_h e l l o_w o r l d.c 3 > A u t h o r: c o u l d t t(f y b y) 4 > M a i l: f u y u n b i y i@g m a i l.c o m 5 > C r e a t e d T i m e: 2013年12月14日 星期六 11时48分50秒 6 ************************************************************************/ 7 8 #i n c l u d e 9 #i n c l u d e 10 #i n c l u d e

11 12 v o i d p r i n t_m e s s a g e_f u n c t i o n (v o i d *p t r)? 13 14 i n t m a i n() 15 { 16 i n t t m p1, t m p2?

实验一 进程同步与互斥

实验一进程同步与互斥 一、实验目的 1.掌握基本的同步与互斥算法,理解生产者消费者模型。 2.学习使用Windows 2000/XP中基本的同步对象,掌握相关API的使用方法。 3.了解Windows 2000/XP中多线程的并发执行机制,实现进程的同步与互斥。 二、实验内容及要求 1.实验内容 以生产者/消费者模型为依据,在Windows 2000环境下创建一个控制台进程,在该进程 中创建n个线程模拟生产者和消费者,实现进程(线程)的同步与互斥。 2.实验要求 , 学习并理解生产者/消费者模型及其同步/互斥规则; , 学习了解Windows同步对象及其特性; , 熟悉实验环境,掌握相关API的使用方法; , 设计程序,实现生产者/消费者进程(线程)的同步与互斥; , 提交实验报告。 三、相关知识介绍 1.同步对象 同步对象是指Windows中用于实现同步与互斥的实体,包括信号量(Semaphore)、互斥量(Mutex)、临界区(Critical Section)和事件(Events)等。本实验中使用到信号量、互斥量和临

界区三个同步对象。 同步对象的使用步骤: , 创建/初始化同步对象。 , 请求同步对象,进入临界区(互斥量上锁)。 , 释放同步对象(互斥量解锁)。 这些对象在一个线程中创建,在其他线程中都可以使用,实现同步与互斥。 2.相关API的功能及使用 我们利用Windows SDK提供的API编程实现实验题目要求,而VC中包含有Windows SDK的所有工具和定义。要使用这些API,需要包含堆这些函数进行说明的SDK头文件——最常见的是Windows.h(特殊的API调用还需要包含其他头文件)。 下面给出的是本实验使用到的API的功能和使用方法简单介绍。 (1) CreateThread , 功能——创建一个在调用进程的地址空间中执行的线程 , 格式 HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParamiter, DWORD dwCreationFlags, Lpdword lpThread ); , 参数说明 lpThreadAttributes——指向一个LPSECURITY_ATTRIBUTES(新线程的安全性描述符)。

同步和互斥的区别

信号量互斥量条件变量 同步和互斥 一、信号量:用于进程间通信(linux中用于线程) 独立于进程在两个进程中都要初始化,若一个已创建,则另一个获取可以执行多次v操作,然后再执行p操作 信号量的一些概念: 以下是信号灯(量)的一些概念: 信号灯与互斥锁和条件变量的主要不同在于”灯”的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于”等待”操作,即资源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持灯亮状态。当然,这样的操作原语也意味着更多的开销。 信号灯的应用除了灯亮/灯灭这种二元灯以外,也可以采用大于1的灯数,以表示资源数大于1,这时可以称之为多元灯。 二、互斥量:(用于在线程间的通信) 1、在一个进程中创建 三、信号量与互斥量的区别 1. 互斥量用于线程的互斥,信号量用于线程的同步。 这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源 以上区别是主要想记住的。 note:信号量可以用来实现互斥量的功能 2. 互斥量值只能为0/1,信号量值可以为非负整数。 也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。3、互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。 4、作用域 信号量: 进程间或线程间(linux仅线程间) 互斥锁: 线程间 5、上锁时 信号量: 只要信号量的value大于0,其他线程就可以sem_wait(p操作-1)成功,成功后信号量的value减一。若value值不大于0,则sem_wait

相关主题
文本预览
相关文档 最新文档