1、 临界区管理实现本组组员:周琪皓,董泉伟,钟佳锋,张倬慎 0 引言随着多处理机体系结构的演变和分布式与并行系统的发展,并发多任务的程序设计技术已愈来愈显得重要,多线程设计模式在这些技术的发展中起着重要作用。在现代操作系统中,利用进 (线 )程间的并发性实现程序中并发成分的并行执行,可大大提高系统的处理能力和效率,但也可能带来诸如执行结果的不确定性等不良现象,因此并发系统中处理好进 (线 )程间的互斥与同步就显得至关重要。 C+语言中的多线程机制是解决线程间的互斥与同步问题的重要工具,其应用 (如网络多媒体应用、工业自动化控制等 )很广泛,很复杂且常易出错。因此在应用程序设计过程中,要考虑多个线
2、程如何同步使用进程的共享资源,如何让一个线程与另一个线程协调合作,以免产生线程间的访问冲突。语言提供的多线程机制能有避免同一共享互斥资源被多个线程同时访问,维护数据的一致性、安全性。生产者 /消费者问题可作为并发进程的同步和互斥问题的一个抽象模型,广泛应用于通信和控制系统中。本文基于 C+语言中的多线程机制,实现操作系统中生产者 /消费者问题,以助人们更好地透解同步概念及其实现方法。1 课程设计目的通过模拟操作者生产者经典问题的实现,以及关于信号量和互斥锁对于多线程的运用,深入理解操作系统中多线程同步法的理论知识 , 加深对教材中的重要算法的理解。同时通过编程实现这些算法 ,更好地掌握操作系统
3、的原理及实现方法 ,提高综合运用各专业课知识的能力。2 课程设计题目和要求2.1 课程设计题目题目 : 临界区管理实现 .2.2课程设计目的与要求初始条件:1.操作系统:Windows2.程序设计语言:C+语言3.有界缓冲区内设有 20 个存储单元,其初值为 0。放入取出的数据项按增序设定为 120 这 20 个整型数。技术要求:1、 生产者和消费者各有两个以上。多个生产者或多个消费者之间须有共享对缓冲区进行操作的函数代码。每个生产者和消费者对有界缓冲区进行操作后,即时显示有界缓冲区的全部内容,当前指针位置。2、 编写多线程同步方法解决生产者 -消费者的程序,并完成对进程进行模拟同步和互斥的控
4、制。2 设计总体思路2.1 多线程编程思想编写 Windows 下的多线程程序,需要使用头文件 pthread.h 以及windows.h.在 LINUX 下进行多线程编程首先要用到CreateThread()这个函数 .函 数 CreateThread()用 来 创 建 一 个 线 程 ,它 的 原 型 为 :HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, / pointer to security attributesDWORD dwStackSize,/ initial thread stack sizeLPTH
5、READ_START_ROUTINE lpStartAddress, / pointer to thread functionLPVOID lpParameter,/ argument for new threadDWORD dwCreationFlags,/ creation flagsLPDWORD lpThreadId);/ pointer to receive thread ID第一个参数是指向 SECURITY_ATTRIBUTES型态的结构的指针。在 Windows 98 中忽略该参数。在 Windows NT 中,它被设为NULL。第二个参数是用于新线程的初始堆栈大小,默认值为
6、0。在任何情况下, Windows 根据需要动态延长堆栈的大小。第三个参数是指向线程函数的指标。函数名称没有限制,但是必须以下列形式声明:DWORD WINAPI ThreadProc (PVOID pParam) ;第四个参数为传递给 ThreadProc 的参数。这样主线程和从属线程就可以共享数据。第五个参数通常为 0,但当建立的线程不马上执行时为旗标CREATE_SUSPENDED。线程将暂停直到呼叫 ResumeThread来恢复线程的执行为止。第六个参数是一个指标,指向接受执行绪 ID 值的变量。2.1.1线 程 数 据在 单 线 程 的 程 序 里 , 有 两 种 基 本 的 数
7、据 : 全 局 变 量 和 局 部 变量 。 但 在 多 线 程 程 序 里 , 还 有 第 三 种 数 据 类 型 : 线 程 数 据 。它 和 全 局 变 量 很 象 , 在 线 程 内 部 , 各 个 函 数 可 以 象 使 用 全 局变 量 一 样 调 用 它 , 但 它 对 线 程 外 部 的 其 它 线 程 是 不 可 见 的 。这 种 数 据 的 必 要 性 是 显 而 易 见 的 。 例 如 我 们 常 见 的 变 量errno, 它 返 回 标 准 的 出 错 信 息 。 它 显 然 不 能 是 一 个 局 部 变 量 ,几 乎 每 个 函 数 都 应 该 可 以 调 用 它
8、 ; 但 它 又 不 能 是 一 个 全 局 变量 , 否 则 在 A 线 程 里 输 出 的 很 可 能 是 B 线 程 的 出 错 信 息 。ThreadHandle0=CreateThread(NULL,0,Producer,NULL,0,用 来 生 成 一 个 互 斥 锁 .NULL参 数 表 明 使 用 默 认 属 性 .如 果 需 要 声 明 特 定 属 性 的 互 斥 锁 , 须调 用 函 数 CreateMutex(NULL,FALSE,NULL)WaitForSingleObject(mutex,INFINITE)声 明 开 始 用 互 斥 锁 上 锁 ,直 至 调 用 Re
9、leaseMutex(mutex)为 止 , 均 被 上 锁 ,即 同 一 时 间 只 能 被 一 个 线 程 调 用 执 行 .当 一 个 线 程 执 行 到pthread_mutex_lock 处 时 , 如 果 该 锁 此 时 被 另 一 个 线 程 使 用 ,那 么 此 线 程 被 阻 塞 , 即 程 序 将 等 待 到 另 一 个 线 程 释 放 此 互 斥锁 .2.1.3 信 号 量信 号 量 本 质 上 是 一 个 非 负 的 整 数 计 数 器 , 它 被 用 来 控 制 对公 共 资 源 的 访 问 。 当 公 共 资 源 增 加 时 , 调 用 函 数aitForSingl
10、eObject(empty,INFINITE)增 加 信 号 量 。 只 有 当 信 号 量值 大 于 时 , 才 能 使 用 公 共 资 源 , 使 用 后 , 函 数WaitForSingleObject(full,INFINITE)减 少 信 号 量 。函 数 ReleaseSemaphore(full,1,NULL)用 来 增 加 信 号 量 的 值 。当 有 线 程 阻 塞 在 这 个 信 号 量 上 时 , 调 用 这 个 函 数 会 使 其 中 的一 个 线 程 不 在 阻 塞 , 选 择 机 制 同 样 是 由 线 程 的 调 度 策 略 决 定的 。 函 数 ReleaseS
11、emaphor()用 来 释 放 信 号 量 。2.2 设计原理生 产 者 线 程 和 消 费 者 线 程 共 享 同 一 个 缓 冲 队 列 , 生 产 者 线 程向 缓 冲 区 中 写 数 据 , 消 费 者 线 程 从 缓 冲 区 中 取 数 据 。 但 两 者必 须 在 使 用 缓 冲 队 列 资 源 时 保 持 互 斥 , 否 则 可 能 会 导 致 在 写入 时 产 生 数 据 覆 盖 , 在 读 出 时 得 到 错 误 数 据 。 因 而 要 在 程 序中 设 置 一 个 互 斥 锁 或 公 用 信 号 量 , 用 于 保 证 线 程 间 的 互 斥 执行 。 同 时 生 产 者
12、 线 程 和 消 费 者 线 程 必 须 保 持 同 步 关 系 , 因 为生 产 者 线 程 的 执 行 为 消 费 者 线 程 提 供 了 需 要 的 数 据 , 是 其 执行 的 前 提 。 反 之 , 消 费 者 线 程 的 执 行 为 生 产 者 线 程 腾 出 了 空闲 的 缓 冲 单 元 , 为 写 数 据 提 供 了 条 件 。 即 消 费 者 线 程 执 行 的前 提 : 缓 冲 队 列 中 至 少 有 一 个 单 元 有 数 据 ; 生 产 者 线 程 执 行的 前 提 : 缓 冲 队 列 中 至 少 有 一 个 单 元 是 空 的 。 在 设 计 过 程 中 ,利 用 信
13、 号 量 和 wait 、 signal 原 语 操 作 来 实 现 。 如 图 1 所 示 :图 1 生 产 者 、 消 费 者 共 享 有 界 缓 冲 区2.3 原语操作实现The structure of the producer processdo / 生产产品wait (empty);wait (mutex);/ 往 Buffer 中放入产品signal (mutex);signal (full); while (true);The structure of the consumer processdo wait (full);wait (mutex);/ 从 Buffer 中取出产
14、品signal (mutex);signal (empty);/ 消费产品 while (true);3 开发环境与工具系统平台: Windows 环境实现语言: C+语言开发工具: Vs20124 概要设计4.1 数据结构设计通过分析课程设计要求,具体设计出如下数据结构 :1. int buffer20=0;/定义缓冲区空间大小2.包含数据结构 pthread_t 它记录一个线程的号,主要包括下面几个函数,完成不同的功能:ThreadHandle0=CreateThread(NULL,0,Producer,NULL,0, /创建一个线程。ExitThread(0);CloseHandle(T
15、hreadHandle0);/等待一个线程结束。4.2 程序模块实现4.2.1 生产者( Producer)模块 生产者线程向一缓冲区中写入数据,且写入缓冲区的数目不能超过缓冲区容量。当生产者产生出数据,需要将其存入缓冲区之前,首先检查缓冲区中是否有 “空 ”存储单元,若缓冲区存储单元全部用完,则生产者必须阻塞等待,直到消费者取走一个存储单元的数据,唤醒它。若缓冲区内有 “空 ”存储单元,生产者需要判断此时是否有别的生产者或消费者正在使用缓冲区,若是有,则阻塞等待,否则,获得缓冲区的使用权,将数据存入缓冲区,释放缓冲区的使用权,其流程图如图 2 所示:生产一条数据是否可用存储单元等待资源 ,
16、阻塞被唤醒是否可用存入一条数据 等待使用权 , 阻塞被唤醒归还使用权数据单元加 1 ,唤醒消费者Y e sN oN oY e s图 2 生 产 者 流 程 图/生 产 者 线 程DWORD WINAPI Producer(LPVOID lpPara)doWaitForSingleObject(empty,INFINITE); /空 缓冲区减 1WaitForSingleObject(mutex,INFINITE); /信号量上 锁bufferin=in+1; /往 Buffer中放入 产 品in=(in+1)%BUFFER_SIZE; /放入指 针调 整, 为 下次送出做准 备printAll
17、();ReleaseMutex(mutex); /信号量解 锁ReleaseSemaphore(full,1,NULL); /满缓 冲区加 1,即当公共 资 源增加 时 , 调 用函数ReleaseSemaphore()增加信号量while(1);4.2.2 消费者 (Consumer)模块 消费者线程从缓冲区中读取数据,且消费者读取的数目不能超过生产者写入的数目。消费者取数据之前,首先检查缓冲区中是否存在装有数据的存储单元,若缓冲区为 “空 ”,则阻塞等待,否则,判断缓冲区是否正在被使用,若正被使用,若正被使用,则阻塞等待,否则,获得缓冲区的使用权,进入缓冲区取数据,释放缓冲区的使用权。其执
18、行流程如图 3 所示:是否可用存储单元 等待资源 , 阻塞被唤醒是否可用取出一条数据等待使用权 , 阻塞被唤醒归还使用权空缓冲区加 1 ,唤醒一个生产者Y e sN oN oY e s消费数据图 3 消费者流程图/消 费 者 线 程DWORD WINAPI Consumer(LPVOID lpPara)doWaitForSingleObject(full,INFINITE); /满缓 冲区减 1WaitForSingleObject(mutex,INFINITE); /信号量上 锁bufferout=0; /从 Buffer中取出 产 品out=(out+1)%BUFFER_SIZE; /取指
19、 针调 整,为 下次取做准 备printAll();ReleaseMutex(mutex); /信号量解 锁ReleaseSemaphore(empty,1,NULL); /空 缓 冲区加1while(1);5 详细设计5.1 源 程 序 代 码#include#include #include #include #include using namespace std;DWORD WINAPI Producer(LPVOID);DWORD WINAPI Consumer(LPVOID);#define WINAPI_stdcall #define THREAD_NUM 20#define B
20、UFFER_SIZE 20 /20个 缓 冲区int buffer20=0;HANDLE empty;HANDLE full;HANDLE mutex; /for mutual exclusion进 程信号量int in=0; /point to the next free positonint out=0; /point to the first full positon/把所有的 缓 冲区 输 出到屏幕上void printAll()int i;for(i=0;i以及 两个库。通过这次课程设计 ,不但加深了对操作系统这们课程的认识 ,而且还了解了操作系统中使用信号量解决生产者 消费者问题算
21、法的实现。比如:用信号量解决生产者 消费者问题时,可以通过一个有界缓冲区 (用数组来实现,类似循环队列 )把生产者和消费者联系起来。假定生产者和消费者的优先级是相同的,只要缓冲区未满,生产者就可以生产产品并将产品送入缓冲区。类似地,只要缓冲区未空,消费者就可以从缓冲区中去走产品并消费它。为了解决生产者 /消费者问题,应该设置两个资源信号量,其中一个表示空缓冲区的数目,用 full 表示,其初始值为有界缓冲区的大小;另一个表示缓冲区中产品的数目,用 empty 表示,其初始值为 0。另外,由于有界缓冲区是一个临界资源,必须互斥使用,所以还需要再设置一个互斥信号量 mutex,起初值为 1。在生产
22、者 /消费者问题中,信号量实现两种功能。首先,它是生产产品和消费产品的计数器,计数器的初始值是可利用的资源数目 (有界缓冲区的长度 )。其次,它是确保产品的生产者和消费者之间动作同步的同步器。生产者要生产一个产品时,首先对资源信号量 full 和互斥信号量 mute 进行操作,申请资源。如果可以通过的话,就生产一个产品,并把产品送入缓冲区。然后对互斥信号量 mutex 和资源信号量 empty 进行操作,释放资源。消费者要消费一个产品时,首先对资源信号量 empty 和互斥信号量 mutex 进行操作,申请资源。如果可以通过的话,就从缓冲区取出一个产品并消费掉。然后对互斥信号量 mutex 和资源信号量 full 进行操作,释放资源。另外,使我们体会最深的是:任何一门知识的掌握,仅靠学习理论知识是远远不够的,要与实际动手操作相结合才能达到功效。短短的课程设计就要结束了,不但对专业知识有了更深的理解,更使自己认识到实践的重要性,理论、实践相结合才能达到很好的学习效果,特别是程序语言的学习。