admin 管理员组

文章数量: 1184232


2024年3月13日发(作者:python文档中文版)

维普资讯

期 

2 00 6 

月 

计算机技术与发展 

(’()MPUTER TECHNOL(X;Y AND DEVEI ̄.)PMENT 

16 No,3 

Mar.2006 

多线程编程技术实现经典进程同步问题 

王文磊,徐汀荣 

(苏州大学计算机科学与技术学院,江苏苏州215006) 

摘要:介绍了Windows环境下进行多线程编程的意义,重点讨论了c++Builder环境下开发多线程应用程序这一问题, 

并通过实现生产者一消费者问题、读者一写者问题、哲学家共餐问题等经典进程同步问题使人们更好地理解同步概念及其 

实现方法。 

关键词:多线程;生产者一消费者;读者一写者;哲学家共餐 

中圈分类号:1 ll 文献标识码:A 文章编号:1005—3751(2006)03—0110—03 

Realize Classical Process SynchrOnizati0n 

Problem Through Multi‘_。threaded Techniques 

WANG Wen—lei,XU Ting—rong 

(College of Computer Science and Technology,Soochow University,Suzhou 215006,China) 

Abstract:Introduce the rise8 of multi—threaded programming in Windows,emphasize multi—threaded application program methods in C 

++Builder environment in details.and help u8 understand the conception of synchronization and its implementation through realizing Pro— 

ducer—Customer Problem,Reader—Writer Problem nd Diannig phinlosophers pmblem. 

Keywords:multi——threaded;producer.customer;reader——writer;dinnig phinlosophers 

O引 言 

多线程的应用广泛,作用也很大,例如在工业自动化 

控制、数据的后台查询、图形处理等方面。在单线程的应 

用程序中,进程中的所有资源都由一个线程来使用。而在 

多线程的应用程序中,进程的多个线程共同存在于进程的 

法知道在什么时候、在什么地方线程被打断,这样如何保 

证线程之间不破坏彼此的数据就显得格外重要[3l。同步 

问题是如此重要,也相当有趣,因而吸引了不少学者对它 

进行研究,由此产成了一系列经典的进程同步问题。其中 

较有代表性的是“生产者一消费者问题”、“读者一写者问 

虚拟地址空间中,它们共享进程的所有资源。因此,如果 

发生多个线程同时访问或者操作一个资源时,由于操作系 

统会把每个线程当作是互不相干的任务分别执行,将会使 

应用程序产生意想不到的结果。因此在应用程序设计过 

程中,要考虑多个线程如何同步使用进程的共享资源,以 

免线程间产生冲突…。 

1 同步 

题”“哲学家共餐问题”等。文中简要讨论了C++Builder 

平台下如何利用多线程编程技术实现上述经典进程同步 

问题,帮助人们更好地理解同步概念及其实现方法。 

2经典进程同步问题 

2.1生产者一消费者问题 

生产者一消费者问题是一个著名的进程同步问题。 

它描述的是:有一群生产者进程在生产消息,并将此消息 

提供给消费者进程去消费。为使生产者进程和消费者进 

程能并发进行,在他们之间设置了一个具有N个缓冲区 

的缓冲池,生产者进程可以将它所生产的消息放入一个缓 

冲区中,消费者进程可以从一个缓冲区中取得一个消息消 

费。尽管所有的生产者进程和消费者进程都是以异步方 

式进行的,但他们之间必须保持同步,即不允许消费者进 

程到一个空的缓冲区中去取消息,也不允许生产者进程向 

个已装满消息且尚未被取走消息的缓冲区中投放消息。 

读者一写者问题的读写操作限制(包括读者优先和写 

者优先): 

撰写多线程程序的一个最具挑战性的问题就是:如何 

让一个线程和另一个线程合作l2 J。这引出了一个非常重 

要的问题:同步。所谓同步是指进程、线程间相互通信时 

避免破坏各自数据的能力。Windows环境下的同步问题 

是由Win32系统的CPU时间片分配方式引起的。虽然在 

某一时刻,只有一个线程占用CPU(单CPu)时间,但是无 

收稿目期:2o05—06—02 

作者简介:王文磊(1979一),男,江苏无锡人。硕士研究生,研究方向 

为数据库和网络;徐汀荣,教授,硕士生导师,研究方向为算法设计与 

分析、网络与数据库、图像处理。 

2.2读者一写者问题 

维普资讯

第3期 王文磊等:多线程编程技术实现经典进程同步问题 

1)写一写互斥:不能有两个写者同时进行写操作。 

2)读一写互斥:不能同时有一个线程在读,而另一个 

线程在写。 

于nlutex,但它还有一个计数器与之关联。创建信号量 

时,需给计数器设置一个最大值。计数器的取值范围就在 

0到最大值之间。如果计数器值大于0,信号量发信号,等 

待函数将继续进行。如果计数器值为0,信号量不发信 

号,等待函数将阻塞,直到另一个线程释放信号量,count 

值增加大于O时为止。当调用释放函数时,应给信号量计 

数器指定被增加的值的大小。 

3)读一读允许:可以有一个或多个读者在读。 

读者一写者问题,它为数据库访问建立了一个模型。 

例如,设想一个飞机定票系统,其中有许多竞争的进程试 

图读写其中的数据。多个进程同时读是可以接受的,但如 

果一个进程正在更新数据库,则所有其他进程都不能访问 

4)事件(Event)。 

事件是一个用途广泛的发信号系统。当一个进程或 

数据库,即使读操作也不行。 

2.3哲学家共餐问题 

由Dijkstra提出并解决的哲学家共餐问题(The Din. 

ning Philosophers Problem)是典型的同步问题。该问题是 

描述有5个哲学家共用一张圆桌,分别坐在周围的5张椅 

子上,在圆桌上有5个碗和5只筷子,他们的生活方式是 

线程需要给另一个进程或线程发信号时,就可使用事件对 

象。首先调用CreateEvent函数创建一个事件,当调用 

WaitForSingleObject等待一个自动事件时,线程将一直阻 

塞,直到事件发出信号为止,一旦事件发出信号,等待函数 

返回,线程继续进行。等待函数自动将事件设置为不发信 

交替地进行思考和进餐。平时,一个哲学家进行思考,饥 

饿时便试图取用其左右最靠近他的筷子,只有在他拿到两 

只筷子时才能进餐。进餐毕,放下筷子继续思考_4 J。哲学 

号,该线程“拥有”这个事件对象。当线程使用完事件对象 

后,调用SetEvent,给其他线程发出信号。 

家共餐问题对于多个竞争进程互斥地访问有限资源(如 

I/O设备)这一类问题的建模十分有用。 

4多线程应用程序编程实例 

4.1生产者一消费者问题 

fastcall TForml::TForml(TC ̄mponent*Owner) 

:TForm(Owner) 

{ 

3 C++Builder多线程应用程序编程基础 

3.1使用C++Builder提供的Trhread类 

VCL类库提供了用于线程编程的TThread类。在 

ead类中封装了Windows中关于线程机制的Win— 

dowsAPI。对于大多数的应用程序来说,可在应用程序中 

InitializeCfiticalSection(&c ̄tSec); 

full=CreateSemaphore(0,0,MAX-BUFFERS,0);//信号量 

full 

使用线程对象来表示执行线程。线程对象通过封装使用 

线程所需的内容,简化了多线程应用程序的编写。 

3.2四种同步机制 

Win32API提供了四种不同的同步机制 J: 

1)临界区(Critical sectiOn)。 

empty=Creat ̄phore(0,MAX-BUFFERS,MAX- 

BUFFERS,0);//信号量empty 

} 

//生产者线程 

void—fastcall TProdueeThread: ̄Execute() 

{ 

临界区是指一次只能有一个线程在其上执行的代码 

区域。Win32API提供了4种函数来实现临界区。当创建 

个临界区时,先声明一个CRITICAL—SECTION型变 

for(int i=O;i<10;i++) 

{ 

WaitForSingleObjeet(Forml一>empty.INFINITE);//生产 

量,并调用InitializzCriticalSection函数对该变量进行初始 

化。在临界区代码的起始位置,使用EntexCriticalSection, 

并在其结束位置,调用LceaveCriticalSection。临界区变量 

使用完毕后,调用DeleteCriticalSection。 

2)互斥体(Mutex)。 

者线程等待empty信号量 

EnterCriticalSection(&Forml一>critSec); 

互斥体提供了多个进程间对一段代码或一个变量的 

互斥访问。通常人们用不同的思考方式来考察互斥和临 

界区。一般把互斥与全局资源联系起来,而把临界区看成 

j=Forml >Product 

strResult=IntToStr(i十1); 

Forml一>Producrs[j]=i十1;/ 缓Wvroe_ ̄-个值 

Forml一>Product++: 

个代码段。首先用CreateMutex函数创建一个mutex 

Synchronize(ShowResuh);//显示当前生产者、消费者、缓 

冲区状态 

LeaveCfitiealSeetion(&Forml一>critSec); 

对象。CreateMutex返回一个句柄。其次,调用WaitForS. 

ingleObject后线程拥有互斥对象,别的线程不能进入代码 

段,直到进入发信号状态。最后,调用ReleaseMutex函数, 

释放互斥对象。 

Rel ̄phore(Forml一>full,1,COunt);//±曾加full信 

号量计数器,使其发信号 

Sleep(1000); 

} 

3)信号量(Semaphore)。 

个信号量是一个mutex的扩展集,其工作方式类似 

维普资讯

・ 

112・ 计算机技术与发展 第l6卷 

l 

//消费者线程 

void—f够tcaU TConsurnerThr ̄d::Execute() 

{ 

for(im i=0;i<20;i++) 

{ 

WaitForSingleObjeet(Forml >full,INFINITE);/梢费者线 

程等待full信号量 

EnterCdticakSeetion(&Form1一>critSec); 

j=Form1一>Product一1; 

s ̄Result=IntToStr(Forml一>Products[j]);//从缓冲 

区中取走一个值 

Form1一>Product一一: 

Synchronize(ShowResult); 

Leav ̄a'iticalSection(&Form1一>critSee); 

Rel ̄phore(Forml一>empty,l,&eotmt);//增加empty 

信号量计数器,使其发信号 

Sleep(500); 

l 

l ‘ 

4.2读者一写者问题 

fasteall TFonnl::TForml( ̄ponent*Owrler) 

:TForm(Owner) 

{ 

m呻 :CreateMutex(0,FALSE,0);//互斥体mutex 

write=Creat ̄phore(0,1,i,0);//信号量write 

l 

//读者线程 

void—fasteaU TReaderThread::Execute() 

{ 

while(!Terminated) 

{ 

Sleep(1000); 

WaitForSingleObjeet(Forml一>mutex,INFINITE); 

Forml一>ReaderCount++: 

if(Form1一>ReaderCoum==1) 

{//检查信号量write,以确保写者线程没有进行写操作 

WaitForSingleObjeet(Forml一>write,INFINITE); 

1 

Synchronize(ShowResult); 

Sleep(100); 

RdeaseMutex(Forml一>mute.x); 

Sleep(1000); 

WaitForSingleObjeet(Forml一>mute】【,INFINITE); 

Forml一>ReaderCount一一: 

Synchronize(ShowResult); 

讧(Form1一>ReaderCotmt=:0) 

{lit后一个读者完成读操作后,释放write信号量,如需 

要写者就可以工作了 

Rel ̄maphore(Forml一>write,1,0); 

l 

ReleaseMutex(Forml一>mutex); 

l 

l 

//q'者线程 

void—fasteall TWriterThread::Execute() 

{ 

while(!Termimted) 

{ 

Sleep(1000); 

//检查信号量write,以确保设有写者线程进行写操作,没有 

读者线程进行读操作 

WaitForSingleObjeet(Forml一>write,INFINITE); 

Synchronize(ShowResult); 

Sleep(2000); 

Synchronize(ClearResuk); 

Rd ̄phore(Fomal一>w-irte,1,0);//释放write信号 

量 

l 

l 

4.3哲学家共餐问题 

fastcall TForml::TForml(TCcmaponent*Owner) 

TForm(Owner) 

{ 

numPhilosophers=5; 

InitializeCridcalSeetion(&critSee); 

fcIr(im i=0;i<may.Philosophers;i++) 

{ 

PhilosoplmrThread[i]=new 1]Philo日。pher1 read(tn把,i); 

chopsticks[i]=CreateMutex(0,FALSE。0); 

csStat[i]=‘0’;//筷子状态,‘O’表示放下,‘1’表示拿起 

pStat[i]=‘0’;/省学家状态,‘0’表示正在思考,‘1’表示正 

在进餐 

l 

l 

void—fasteall TPhilosopherThread::Execute() 

{ 

while(!Terminated) 

{ 

Sleep(random(2000)+i000);/省学家思考 

//饥饿,所以拾起筷子 

cs【O]=Forml一>chopsticks[ThreadlD]; 

cs[1_J=Form1一>chopsticks[(ThreadlD+1)%hum]; 

WaitForMultipleObjeets(2,∞,TRUE,INFINITE); 

//要求哲学家必须同时拿起两支筷子 

(下转第儿5页) 

维普资讯

第3期 赵广利等:EJB体系结构性能的改进及应用 ・ ll5 ・ 

4结束语 

EJB是服务器端组件开发的基本规范_5j, 

它采取“分而治之”和让“恰当的专家做恰当 

的事情”的方式来解决服务器端应用问题。 

EJB技术不仅使得复杂的分布式多层结构应 

用系统开发变得容易,而且为进行软件重用 

和提高软件质量提供了一条新的解决途径。 

虽然分布式通信服务器的服务器端开发有时 

可能会比较复杂,但由于容器提供了大部分 

针对通信、状态管理、资源分配与线程管理底 

层架构代码编写的支持,因此极大地简化了 

J2EE/E 的服务器端开发。EJB作为一种开 

图2改进后的体系结构 

重用性和灵活性。将来若系统增加了新的数据资源,只要 

放的体系结构,仍在不断的发展和完善,许多 

新的技术、新的概念层出不穷。因而在某些方面可能还不 

更改数据访问层的具体实现就行了,其它部分不用更改, 

够成熟,但这正是日B技术的生命力和吸引力所在。 

提高了系统的可扩展性。 

(2)工作流对象层负责向客户端对象提供标准的访问 

参考文献: 

接El,它简化了应用层和Enterprise Bean的接口,降低了 

[1]杨绍方.深入掌握J2EE编程技术[M].北京:科学出版社, 

应用层实现的复杂性。增加工作流对象层后,使得客户端 

2o02. 

对象可以不必了解工作流的实现机制,就可以透明地访问 

[2]禁剑,景楠.Java网络编程:J2EE[M].北京:清华大学 

这些工作流对象,实现对工作流的管理,提高系统的可扩 

出版社,2003.442—459. 

展性。 

[3] 、vis C.实用J2EE设计模式编程指南[M].北京:电子工 

(3)快读通道直接绕开了Enterprise Bean访问数据 

业出版社,2oo3.25—160. 

库,提高了数据访问的效率,减少了系统开销。对经常改 

[4]陈华军.J2EE构建企业级应用解决方案[M].北京:人民邮 

变的数据,使用实体EJB来保证修改的正确性和查询到 

电出版社,2oo2.1—27,43—64. 

最新的数据;对只读数据或很少修改的数据采用快读通道 

[5]班书吴.EJB企业应用与开发实例[M].北京:北京科海电 

模式。 

子出版社,2003.327—372. 

(上接第l12页) 

EnterCriticalSection(&Form1一>critSec); 

5总结 

Forml一>csStat[ThreadID]=‘1’;//修改筷子状态 

重点讨论了C++Builder平台下如何开发多线程应 

Forml一>csStat[(ThreadlD+1)%ntllT1]=‘1’; 

用程序这一问题,通过实现“生产者一消费者问题”、“读者 

Forml一>pStat[ThreadlD]=‘1’;//修改哲学家状态 

写者问题”、“哲学家共餐问题”这一系列经典的进程同 

Synchronize(ShowResult); 

步问题,比较清晰地反映了在Windows环境下进行多线 

LeaveCriticalSection(&Form1一>critSec): 

Sleep(random(500)+500);//进餐 

程编程技术及其实现方法。以上程序均在C++BuM— 

//进餐完毕 

er6.0下调试通过。 

EntetCriticalSection(&Form1一>critSec); 

Forml>pStat[ThreadlD]=‘0’;//修改哲学家状态 

参考文献: 

Rd ̄utex(Forml一>chopsticks[ThreadlD]); 

[1]BrainM.深入学习:Win32系统服务开发与实例[M].张 

Forml一>csStat[ThreadlD]=‘0’;//修改筷子状态 

锦,张俊,等译北京:电子工业出版社,2001, 

Rd ̄utex(Forml一>chopsticsk[(ThreadlD+1)% 

[2]PetzoldC.Windows程序设计(第5版)[M].北京博彦科技 

ntlm]); 

发展有限公司译+北京:北京大学出版社,1999. 

Forml一>c ̄tat[(ThreadlD+1)%ntlrn]=‘0’; 

[3]Richter J.Windows核心编程[M].王建华,等译.北京:机械 

Synchronize(ShowResttlt); 

工业出版社,2000. 

LeaveCriticalSection(&Forml一>ctitSec); 

[41汤子瀛,哲风屏,汤小丹.计算机操作系统[M].西安:西安 

} 

电子科技大学出版社,2000. 

} 

[5]李幼仪,甘志.C++Builder高级应用开发指南[M].北 

京:清华大学出版社,2002. 


本文标签: 线程 问题 进程 进行