分布式系统复习题及参考答案(4)

2019-03-16 22:51

至于RPC的具体实现, 我们可以借助下图3来理解:

图3 RPC的实现概况

其中stub是一组RPC机制的操作原语, 这些原语构成了RPC的实现细节, 它可以独立于client、server编程. 下面我们来解说图3的执行过程:

1) 调用者调用本地stub中的一个过程(开始远程过程调用请求).

2) 这个stub过程把有关的参数组装成一个消息包或一组消息包, 形成一条消息. 运行此执行过程的远程场点的IP地址和执行该过程的进程ID号也包含在这条消息中.

3) 将这条消息发送给对应的RPC runtime(RPC运行库)子程序, 由这个子程序将消息发送到远程场点.

4) 在接收到这条消息时, server端的RPC runtime子程序引用与被调用者对应的stub中的一个子程序, 并让它来处理消息.

5) 与被调用者对应的stub中的这个子程序撤卸消息, 解析出相关参数, 并用本地调用方式执行所指定的过程.

6) 返回调用结果, 调用者对应的stub子程序执行return语句返回到用户, 整个RPC过程结束.

实际上, 从上面这个执行过程中, 我们可以看到RPC的实现主要有两个问题需要解决. 一个是在远程过程调用时, 如何定位远程场点; 另外一个就是相关的两个场点必须能协同工作, 所有这些工作对用户都是透明的, 依次执行.

通常在实际编程中, 程序设计者主要负责设计计算过程并实现计算过程体, 而对应的stub由系统生成. 后面我们就要说到Microsoft的RPC实现机制,看看它是如何产生stub的。 Para2. 如何设计好的RPC

对调用双方来说, 传递RPC参数包括辅助处理本地数据表示和网络数据表示的相互转换. 此外, 输入输出参数需要一些存储分配. 同时, RPC中的等待时间也不能忽略.

所以, 一般来说, 应该尽可能降低调用次数. 例如,如果要对一个大数组的每个元素都执行计算, 我们就可以一次调用处理一整行或者整个数组, 而不用每次调用传递一个元素. 这样可以降低有RPC引入的额外开销. Para3. 应用程序的组件

为了在client和server端使用RPC, 当然少不了client进程和server进程. 另外还有一个名称服务进程, 这个稍后再说. 开发过程大致是这样的:

1) 任何RPC调用都使用一个定义在IDL(interface definition language, 接口定义语言)文件中的接口, 然后MIDL(Microsoft IDL)编译器对IDL文件进行编译, 编译之后会自动生成一个.h文件, 同时生成一个 client stub 和一个server stub. 关于这个你可以在dos下运行midl.exe/?得到更详细的信息.

2) Client端应用程序使用client stub调用RPC runtime以实现网络上的调用. 接下来RPC运行时使用一组DLL中的一个来实现被使用的特定网络协议.

3) Server端也与RPC runtime连接. 不过server端应用程序使用一些其它的函数来将自己作为一个特殊接口的服务器进行注册(向谁注册?), 并开始侦听接口的请求.

3.如何用Java或其它语言解决线程同步与互斥的问题?(任选择一种语言)

同步 有同步方法和同步块

当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。存在一些线程同步地址的问题,Win32提供了许多线程同步的方式。在本节你将看到使用临界区、互斥、信号量和事件来解决线程同步的问题。 1. 临界区

临界区是一种最直接的线程同步方式。所谓临界区,就是一次只能由一个线程来执行的一段代码。如果把初始化数组的代码放在临界区内,另一个线程在第一个线程处理完之前是不会被执行的。在使用临界区之前,必须使用InitializeCriticalSection()过程来初始化它。 其声明如下:

procedure InitializeCriticalSection(var lpCriticalSection: TRLCriticalSection);stdcall;

lpCriticalSection参数是一个TRTLCriticalSection类型的记录,并且是变参。只需要在lpCriticalSection中传递未初始化的记录,InitializeCriticalSection()过程就会填充这个记录。

在记录被填充后,我们就可以开始创建临界区了。这时我们需要用EnterCriticalSection()和 LeaveCriticalSection()来封装代码块。这两个过程的声明如下:

procedure EnterCriticalSection(var lpCriticalSection:TRRLCriticalSection);stdcall; procedure LeaveCriticalSection(var lpCriticalSection:TRRLCriticalSection);stdcall; 正如你所想的,参数lpCriticalSection就是由InitializeCriticalSection()填充的记录。 当你不需要TRTLCriticalSection记录时,应当调用DeleteCriticalSection()过程,下面是它的声明:

procedure DeleteCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall; 2. 互斥

互斥非常类似于临界区,除了两个关键的区别:首先,互斥可用于跨进程的线程同步。其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。

提示临界区与事件对象(比如互斥对象)的最大的区别是在性能上。临界区在没有线程冲突时,要用1 0 ~ 1 5个时间片,而事件对象由于涉及到系统内核要用400~600个时间片。可以调用函数CreateMutex ( )来创建一个互斥量。下面是函数的声明:

function CreateMutext(lpMutextAtrribes:PSecurityAttributtes; bInitalOwner:BOOL; lpName:PChar):THandle; stdcall;

lpMutexAttributes参数为一个指向TSecurityAttributtes记录的指针。此参数通常设为0,表示默认的安全属性。bInitalOwner参数表示创建互斥对象的线程是否要成为此互斥对象的拥有者。当此参数为False时,表示互斥对象没有拥有者。lpName参数指定互斥对象的名称。设为nil表示无命名,如果参数不是设为nil,函数会搜索是否有同名的互斥对象存在。如果有,函数就会返回同名互斥对象的句柄。否则,就新创建一个互斥对象并返回其句柄。当使用完互斥对象时,应当调用CloseHandle()来关闭它。

在程序中使用WaitForSingleObject()来防止其他线程进入同步区域的代码。此函数声明如下: function WaitForSingleObject(hHandle: THandle; dwMilliseconds: DWORD): DWORD; stdcall; 这个函数可以使当前线程在dwMilliseconds指定的时间内睡眠,直到hHandle参数指定的对象进入发信号状态为止。一个互斥对象不再被线程拥有时,它就进入发信号状态。当一个进程要终止时,它就进入发信号状态。dwMilliseconds参数可以设为0,这意味着只检查hHandle参数指定的对象是否处于发信号状态,而后立即返回。dwMilliseconds参数设为INFINITE,表示如果信号不出现将一直等下去。再次声明,当一个互斥对象不再被一个线程所拥有,它就处于发信号状态。此时首先调用WaitForSingleObject()函数

的线程就成为该互斥对象的拥有者,此互斥对象设为不发信号状态。当线程调用ReleaseMutex()函数并传递一个互斥对象的句柄作为参数时,这种拥有关系就被解除,互斥对象重新进入发信号状态。

注意除WaitForSingleObject()函数外,你还可以使用WaitForMultipleObject()和MsgWaitForMultipleObject()函数,

它们可以等待几个对象变为发信号状态。这两个函数的详细情况请看Win32 API联机文档。 3. 信号量

另一种使线程同步的技术是使用信号量对象。它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进入要同步的代码。可以用CreateSemaphore()来创建一个信号量对象,其声明如下:

function CreateSemaphore(lpSemaphoreAttributes: PSecurityAttributes; lInitialCount, lMaximumCount: Longint; lpName: PChar): THandle; stdcall; 和CreateMutex()函数一样,CreateSemaphore()的第一个参数也是一个指向TSecurityAttribute s记录的指针,此参数的缺省值可以设为nil。

lInitialCount参数用来指定一个信号量的初始计数值,这个值必须在0和lMaximumCount之间。此参数大于0,就表示信号量处于发信号状态。当调用WaitForSingleObject()函数(或其他函数)时,此计数值就减1。当调用ReleaseSemaphore()时,此计数值加1。

参数lMaximumCount指定计数值的最大值。如果这个信号量代表某种资源,那么这个值代表可用资源总数。

参数lpName用于给出信号量对象的名称,它类似于CreateMutex()函数的lpName参数。

4.如何用Java或C/C++语言实现多线程?(任选择一种语言)

//这个例子是基于事件对象的 #include #include

DWORD WINAPI Fun1Proc(LPVOID lpParameter);//thread data DWORD WINAPI Fun2Proc(LPVOID lpParameter);//thread data int tickets=100;

HANDLE g_hEvent; void main() {

HANDLE hThread1;

HANDLE hThread2;

//创建人工重置事件内核对象

g_hEvent=CreateEvent(NULL,FALSE,FALSE,\); if (g_hEvent)

{

if (ERROR_ALREADY_EXISTS==GetLastError()) {

cout<<\<

SetEvent(g_hEvent); //创建线程

hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2);

//让主线程睡眠4秒 Sleep(4000);

//关闭事件对象句柄 CloseHandle(g_hEvent); }

//线程1的入口函数

DWORD WINAPI Fun1Proc(LPVOID lpParameter)//thread data {

while (true) {

WaitForSingleObject(g_hEvent,INFINITE); //ResetEvent(g_hEvent); if (tickets>0) {

Sleep(1);

cout<<\<

{

SetEvent(g_hEvent); break; } } return 0; }

//线程2的入口函数

DWORD WINAPI Fun2Proc(LPVOID lpParameter)//thread data {

while (true) {

//请求事件对象

WaitForSingleObject(g_hEvent,INFINITE); //ResetEvent(g_hEvent); if (tickets>0) {

Sleep(1);

cout<<\<

{

SetEvent(g_hEvent); break; } }

return 0; }


分布式系统复习题及参考答案(4).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:07级(2010年毕业试题)简单解决问题修改后练习

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: