操作系统课程设计__用多线程同步方法解决生产者
- 格式:doc
- 大小:254.50 KB
- 文档页数:17
Delphi关于多线程同步的⼀些⽅法(转)线程是进程内⼀个相对独⽴的、可调度的执⾏单元。
⼀个应⽤可以有⼀个主线程,⼀个主线程可以有多个⼦线程,⼦线程还可以有⾃⼰的⼦线程,这样就构成了多线程应⽤了。
由于多个线程往往会同时访问同⼀块内存区域,频繁的访问这块区域,将会增加产⽣线程冲突的概率。
⼀旦产⽣了冲突,将会造成不可预料的结果(该公⽤区域的值是不可预料的)可见处理线程同步的必要性。
注意:本⽂中出现的所有代码都是⽤DELPHI描述的,调试环境为Windows me ,Delphi 6。
其中所涉及的Windows API函数可以从MSDN获得详细的。
⾸先引⽤⼀个实例来引出我们以下的讨论,该实例没有采取任何措施来避免线程冲突,它的主要过程为:由主线程启动两个线程对letters这个全局变量进⾏频繁的读写,然后分别把修改的结果显⽰到ListBox中。
由于没有同步这两个线程,使得线程在修改letters时产⽣了不可预料的结果。
ListBox中的每⼀⾏的字母都应该⼀致,但是上图画线处则不同,这就是线程冲突产⽣的结果。
当两个线程同时访问该共享内存时,⼀个线程还未对该内存修改完,另⼀个线程⼜对该内存进⾏了修改,由于写值的过程没有被串⾏化,这样就产⽣了⽆效的结果。
可见线程同步的重要性。
以下是本例的代码 unit.pas⽂件 unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; //定义窗⼝类 type TForm1 = class(TForm) ListBox1: TListBox; ListBox2: TListBox; Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; //定义线程类 type TListThread=class(TThread) private Str:String; protected procedure AddToList;//将Str加⼊ListBox Procedure Execute;override; public LBox:TListBox; end; //定义变量 var Form1: TForm1; Letters:String='AAAAAAAAAAAAAAAAAAAA';//全局变量 implementation {$R *.dfm} //线程类实现部分 procedure TListThread.Execute; var I,J,K:Integer; begin for i:=0 to 50 do begin for J:=1 to 20 do for K:=1 to 1000 do//循环1000次增加产⽣冲突的⼏率 if letters[j]<'Z' then letters[j]:=succ(Letters[j]) else letters[j]:='A'; str:=letters; synchronize(addtolist);//同步访问VCL可视 end; end; procedure TListThread.AddToList; begin LBox.Items.Add(str);//将str加⼊列表框 end; //窗⼝类实现部分 procedure TForm1.Button1Click(Sender: TObject); var th1,th2:TListThread; begin Listbox1.Clear; Listbox2.Clear; th1:=tlistThread.Create(true);//创建线程1 th2:=tlistThread.Create(true);//创建线程2 th1.LBox:=listBox1; th2.LBox:=listBox2; th1.Resume;//开始执⾏ th2.Resume; end; end. 由上例可见,当多个线程同时修改⼀个公⽤变量时,会产⽣冲突,所以我们要设法防⽌它,这样我们开发的多线程应⽤才能够稳定地运⾏。
Python自带队列模块Queue的使用(1)Python自带队列模块Queue的使用(1)Python自带的queue模块是多线程编程中常用的数据结构之一,用于解决线程间的通信和数据同步问题。
queue模块提供了多种队列类型,包括FIFO队列、LIFO队列和优先级队列,可以根据不同的需求选择合适的队列类型。
在Python中,queue模块中最常用的队列类型是FIFO队列,即先进先出队列。
FIFO队列的特点是,先放入队列的元素先被取出,与现实生活中的排队现象类似。
下面将介绍queue模块的FIFO队列的使用方法。
首先,我们需要导入queue模块:```pythonimport queue```接下来,我们可以创建一个FIFO队列对象:```pythonq = queue.Queue```在创建队列对象之后,我们可以使用队列对象的put(方法往队列中放入元素,使用get(方法从队列中取出元素。
put(方法和get(方法都是阻塞方法,意味着如果队列已满或者队列为空时,put(方法和get(方法都会阻塞当前线程。
下面是一个简单的例子,展示了如何向队列中放入元素和从队列中取出元素:```pythonimport queueimport threadingdef producer(q):for i in range(5):q.put(i)print(f'Produced: {i}')def consumer(q):while True:item = q.getif item is None:breakprint(f'Consumed: {item}')q.task_doneq = queue.Queuet1 = threading.Thread(target=producer, args=(q,))t2 = threading.Thread(target=consumer, args=(q,))t1.startt2.startq.join```在上面的代码中,我们创建了一个队列对象q,并创建了两个线程,一个线程作为生产者使用put(方法向队列中放入元素,另一个线程作为消费者使用get(方法从队列中取出元素。
第1篇一、实验目的1. 理解多线程的概念和作用。
2. 掌握多线程的创建、同步和通信方法。
3. 熟悉Java中多线程的实现方式。
4. 提高程序设计能力和实际应用能力。
二、实验环境1. 操作系统:Windows 102. 开发工具:IntelliJ IDEA3. 编程语言:Java三、实验内容本次实验主要完成以下任务:1. 创建多线程程序,实现两个线程分别执行不同的任务。
2. 使用同步方法实现线程间的同步。
3. 使用线程通信机制实现线程间的协作。
四、实验步骤1. 创建两个线程类,分别为Thread1和Thread2。
```javapublic class Thread1 extends Thread {@Overridepublic void run() {// 执行Thread1的任务for (int i = 0; i < 10; i++) {System.out.println("Thread1: " + i);}}}public class Thread2 extends Thread {@Overridepublic void run() {// 执行Thread2的任务for (int i = 0; i < 10; i++) {System.out.println("Thread2: " + i);}}}```2. 创建一个主类,在主类中创建两个线程对象,并启动它们。
```javapublic class Main {public static void main(String[] args) {Thread thread1 = new Thread1();Thread thread2 = new Thread2();thread1.start();thread2.start();}```3. 使用同步方法实现线程间的同步。
```javapublic class SynchronizedThread extends Thread {private static int count = 0;@Overridepublic void run() {for (int i = 0; i < 10; i++) {synchronized (SynchronizedThread.class) {count++;System.out.println(Thread.currentThread().getName() + ": " + count);}}}}public class Main {public static void main(String[] args) {Thread thread1 = new SynchronizedThread();Thread thread2 = new SynchronizedThread();thread1.start();thread2.start();}```4. 使用线程通信机制实现线程间的协作。
操作系统课程设计报告基于DOS的多任务系统的实现专业:信息工程学院(软件工程)班级:姓名:学号:指导老师:20101223一、课程设计的目的1. 加深多线程和进程概念的理解,明确进程和程序的区别。
2. 加深对CPU调度过程(现场保护、CPU的分派和现场恢复)的理解。
3. 进一步认识并发执行的概念,明确顺序执行和并发执行的区别。
4. 加深对临界资源,临界区,信号量以及同步机制的理解。
5. 加深对消息缓冲通信的理解。
二、课程设计要求1. 用C语言完成线程的创建和撤消,并按优先权加时间片轮转算法对多个线程进行调度;2. 改变时间片的大小,观察结果的变化。
思考:为什么时间片不能太小或太大;3. 假设两个线程共用同一软件资源(如某一变量,或某一数据结构),请用记录型信号量来实现对它的互斥访问;4. 假设有两个线程共享一个可存放5个整数的缓冲,一线程不停的计算1到50的平方,并将结果放入缓冲中,另一个线程不断的从缓冲中取出结果,并将他们打印出来,请用记录型信号量实现这一生产者和消费者的同步问题;5. 实现消息缓冲通信,并于3,4中的简单通信进行比较;6. 思考:在线程间进行消息缓冲通信时,若对消息队列的访问没有满足互斥要求,情况会怎么样?三、程序的设计细想和框图[根据自己的代码写]1.调度算法:用TCB数组下标音隐含把所有线程排成一个循环队列,当时间片到时,有线程正在执行,则停止它,将其变为就绪,把现场信息压入私有栈堆。
2.调度原因:时间片到时,线程执行完毕,正在执行的线程等待某种事件而不能继续执行。
3.时钟中断的截取4. 线程的阻塞及唤醒引起进程阻塞的典型事件为等待操作的完成:相对高速的而言,设备的速度显得较慢,这里引起线程阻塞的典型事件有两个:一是并发执行的线程间临界资源的竞争;二是相互合作的线程间的同步。
一个正在只在执行的线程因某种原因而阻塞时,必须给出因该原因而阻塞的阻塞队列的对手信息,阻塞原语所做的主要工作有:将线程的状态改为阻塞态,将线程插入指定的阻塞队列末尾,并重新进行CPU调度。
多线程同步机制Critical section(临界区)用来实现“排他性占有”。
适用范围是单一进程的各线程之间。
它是:·一个局部性对象,不是一个核心对象。
·快速而有效率。
·不能够同时有一个以上的critical section被等待。
·无法侦测是否已被某个线程放弃。
MutexMutex是一个核心对象,可以在不同的线程之间实现“排他性占有”,甚至几十那些现成分属不同进程。
它是:·一个核心对象。
·如果拥有mutex的那个线程结束,则会产生一个“abandoned”错误信息。
·可以使用Wait…()等待一个mutex。
·可以具名,因此可以被其他进程开启。
·只能被拥有它的那个线程释放(released)。
SemaphoreSemaphore被用来追踪有限的资源。
它是:·一个核心对象。
·没有拥有者。
·可以具名,因此可以被其他进程开启。
·可以被任何一个线程释放(released)。
Ev ent ObjectEv ent object通常使用于overlapped I/O,或用来设计某些自定义的同步对象。
它是:·一个核心对象。
·完全在程序掌控之下。
·适用于设计新的同步对象。
· “要求苏醒”的请求并不会被储存起来,可能会遗失掉。
·可以具名,因此可以被其他进程开启。
Interlocked Variable如果Interlocked…()函数被使用于所谓的spin-lock,那么他们只是一种同步机制。
所谓spin-lock是一种busy loop,被预期在极短时间内执行,所以有最小的额外负担(overhead)。
系统核心偶尔会使用他们。
除此之外,interlocked variables主要用于引用技术。
他们:·允许对4字节的数值有些基本的同步操作,不需动用到critical section或mutex之类。
【关键字】实验java多线程实验报告篇一:西北农林科技大学java多线程实验报告实验7 多线程1.实验目的(1) 掌握Java多线程的概念和实现方法(2) 掌握Java多线程的同步问题2.实验内容任务一:火车售票假设有火车票1000张,创建10个线程模拟10个售票点,每个售票点100毫秒买一张票。
打印出售票过程,注意使用synchronized确保同一张票只能卖出一次。
程序运行结果见左图。
打开EclipseTickets.javapublic class Ticket extends Thread {int ticket =1000; String name =""; public void run(){ while(true){synchronized(name){ if(ticket"第" + Thread.currentThread().getName()+ "售票点卖出了第" + ticket-- + "张票");}} }}} try{ } catch(InterruptedException e){ } Thread.sleep(100);Test.javapublic class Test {} public static void main(String args[]){} Ticket t = new Ticket(); new Thread(t,"1").start(); new Thread(t,"2").start(); new Thread(t,"3").start(); new Thread(t,"4").start(); new Thread(t,"5").start(); new Thread(t,"6").start(); new Thread(t,"7").start(); new Thread(t,"8").start(); new Thread(t,"9").start(); new Thread(t,"10").start();任务二:银行存款假设某家银行,它可接受顾客的汇款,每做一次汇款,便可计算出汇款的总额。
C#实现多线程的同步⽅法详解本⽂主要描述在C#中线程同步的⽅法。
线程的基本概念⽹上资料也很多就不再赘述了。
直接接⼊主题,在多线程开发的应⽤中,线程同步是不可避免的。
在.Net框架中,实现线程同步主要通过以下的⼏种⽅式来实现,在MSDN的线程指南中已经讲了⼏种,本⽂结合作者实际中⽤到的⽅式⼀起说明⼀下。
1. 维护⾃由锁(InterLocked)实现同步2. 监视器(Monitor)和互斥锁(lock)3. 读写锁(ReadWriteLock)4. 系统内核对象1) 互斥(Mutex), 信号量(Semaphore), 事件(AutoResetEvent/ManualResetEvent)2) 线程池除了以上的这些对象之外实现线程同步的还可以使⽤Thread.Join⽅法。
这种⽅法⽐较简单,当你在第⼀个线程运⾏时想等待第⼆个线程执⾏结果,那么你可以让第⼆个线程Join进来就可以了。
⾃由锁(InterLocked)对⼀个32位的整型数进⾏递增和递减操作来实现锁,有⼈会问为什么不⽤++或--来操作。
因为在多线程中对锁进⾏操作必须是原⼦的,⽽++和--不具备这个能⼒。
InterLocked类还提供了两个另外的函数Exchange, CompareExchange⽤于实现交换和⽐较交换。
Exchange操作会将新值设置到变量中并返回变量的原来值: int oVal = InterLocked.Exchange(ref val, 1)。
监视器(Monitor)在MSDN中对Monitor的描述是: Monitor 类通过向单个线程授予对象锁来控制对对象的访问。
Monitor类是⼀个静态类因此你不能通过实例化来得到类的对象。
Monitor 的成员可以查看MSDN,基本上Monitor的效果和lock是⼀样的,通过加锁操作Enter设置临界区,完成操作后使⽤Exit操作来释放对象锁。
不过相对来说Monitor的功能更强,Moniter可以进⾏测试锁的状态,因此你可以控制对临界区的访问选择,等待or离开, ⽽且Monitor还可以在释放锁之前通知指定的对象,更重要的是使⽤Monitor可以跨越⽅法来操作。
第4章线程管理与控制4.1 线程概念简介每个进程都拥有自己的数据段、代码段和堆栈段,这就造成了进程在进行切换等操作时都需要有比较复杂的上下文切换等动作。
为了进一步减少处理机的空转时间,支持多处理器以及减少上下文切换开销,进程在演化中出现了另一个概念——线程。
它是进程独立的一条运行路线,处理器调度的最小单元,也可以称为轻量级进程。
线程可以对进程的存空间和资源进行访问,并与同一进程中的其他线程共享。
因此,线程的上下文切换的开销比创建进程小很多。
同进程一样,线程也将相关的执行状态和存储变量放在线程控制块(TCB)。
一个进程可以有多个线程,也就是有多个线程控制块及堆栈寄存器,但却共享一个用户地址空间。
要注意的是,由于线程共享了进程的资源和地址空间,因此,任何线程对系统资源的操作都会给其他线程带来影响。
由此可知,多线程中的同步是非常重要的问题。
在多线程系统中,进程与进程的关系如图所示。
进程与线程关系4.2 Linu*多线程编程API与实验任务4.3.1 Linu*多线程编程API创建线程pthread_create()函数实际上就是确定调用该线程函数的入口点,在线程创建以后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出一种方法。
另一种退出线程的方法是使用函数pthread_e*it(),这是线程的主动行为。
这里要注意的是,在使用线程函数时,不能随意使用e*it()退出函数进行出错处理,由于e*it()的作用是使调用进程终止,往往一个进程包含多个线程,因此,在使用e*it()之后,该进程中的所有线程都终止了。
因此,在线程中就可以使用pthread_e*it()来代替进程中的e*it()。
由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放。
正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。
湖南科技大学计算机科学与工程学院操作系统课程设计报告学号:姓名:班级:目录实验一.........................................................一、实验题目.............................................二、实验目的.............................................三、总体设计.............................................一、实验题目二、实验目的.............................................三、总体设计.............................................四、详细设计.............................................五、实验结果与分析.......................................六、小结与心得体会.......................................实验四.........................................................一、实验题目.............................................二、实验目的.............................................三、总体设计.............................................四、详细设计.............................................三、总体设计.............................................四、详细设计.............................................五、实验结果与分析.......................................六、小结与心得体会....................................... 实验七.........................................................一、实验题目.............................................二、实验目的.............................................三、总体设计.............................................四、详细设计.............................................五、实验结果与分析.......................................2)通过创建进程、观察正在运行的进程和终止进程的程序设计和调试操作,进一步熟悉操作系统的进程概念,理解 Windows进程的“一生”。
操作系统同步的概念引言操作系统是计算机系统中最核心的软件之一,它负责协调和管理计算机硬件资源的分配和使用。
其中,同步机制是操作系统中非常重要的一个概念,它用于保证进程之间的正确顺序执行和共享资源的访问。
本文将深入探讨操作系统同步的概念、机制和应用。
什么是同步在计算机科学中,同步(Synchronization)是指两个或多个进程之间的协调和合作,以达到一致的状态或执行顺序。
在多道程序系统中,进程并发执行,由于资源有限,进程之间可能产生冲突。
同步机制就是为了解决这些冲突,保证进程之间能够按照既定的顺序访问共享资源。
同步的重要性同步在操作系统中具有重要的意义和作用。
首先,同步机制可以确保进程之间按照正确的顺序执行。
如果没有同步机制,进程的执行顺序可能是随机的,导致程序出现各种错误。
其次,同步可以避免资源竞争。
多个进程同时访问一个共享资源时,如果没有同步机制的保护,可能导致数据的不一致和错误的结果。
最后,同步还可以提高系统的效率。
通过合理地使用同步机制,可以避免进程的无谓等待和忙等问题,提高系统的并发性和响应性。
同步机制分类同步机制可以分为两类:互斥同步和条件同步。
互斥同步互斥同步是指只允许一个进程或线程访问共享资源,其他进程或线程必须等待。
互斥同步可以通过锁机制实现,常见的锁包括互斥锁(Mutex)和信号量(Semaphore)。
互斥同步可以有效地避免进程的竞争和冲突,保证资源的安全访问。
条件同步条件同步是指根据某个条件来确定进程的执行顺序。
在条件同步中,进程可能会等待一个条件的满足,当条件满足时才继续执行。
条件同步可以通过条件变量来实现,常见的条件变量有信号量(Semaphore)和事件(Event)。
条件同步可以用于进程之间的协作和消息传递,实现复杂的同步逻辑。
同步机制的应用同步机制在操作系统中有广泛的应用。
下面列举了几个常见的应用场景:生产者-消费者问题生产者-消费者问题是计算机科学中的一个经典问题,它涉及到多个进程对有限资源的访问。
《操作系统》课程教学大纲一、教学大纲说明(一)课程的地位、作用与任务《操作系统》是信息类计算机应用专业学生必修的公共基础课之一,是一门涉及较多硬件知识的计算机系统软件课程。
在计算机软硬件课程的设置上,它起着承上启下的作用。
其特点是概念多、较抽象和涉及面广,其整体实现思想和技术又往往难于理解。
操作系统对计算机系统资源实施管理,是所有其他软件与计算机硬件的唯一接口,所有用户在使用计算机时都要得到操作系统提供的服务。
因此本课程的目的与任务是使学生通过本课程的学习,理解操作系统的基本概念和主要功能,掌握常用操作系统(如UNIX、xinux)的使用和一般管理方法,了解它是如何组织和运作的,从而为学生以后的学习和工作打下基础。
先修课要求,本课程在学习之前最好具有计算机组成原理、程序设计语言的知识。
(二)课程的教学目的和要求通过本课程的学习,使学生较好地掌握操作系统在计算机系统中的重要作用和基本工作原理。
了解操作系统发展过程、新技术的应用。
理解操作系统的基本概念和主要功能,掌握常用操作系统及它们的使用和一般管理方法,为今后的进修、应用实务作好技术准备。
掌握:基本概念包括:多道程序设计、并发、分时、作业、进程、互斥与同步、周转时间、吞吐量、重定位、连接、虚拟存储、虚拟设备、死锁、线程。
基本知识:计算机系统资源(处理机、存储器、设备、文件)的管理策略。
基本技能:管理系统资源的常用命令、系统配置与维护的方法和技术。
理解重点:进程概念、存储管理(尤其虚拟存储)的各种策略、文件系统的管理、设备的管理和配置。
结合具体现代操作系统加深理解。
了解难点:进程的概念及其与程序的区别、进程间同步与互斥的正确实现、虚拟设备与虚拟存储。
Unix,Windows NT的系统结构、功能特点。
(三)课程的教学方法与手段1、本课程概念多、较抽象、涉及面广,因此教学形式以讲授方式为主。
约佔80%。
实验与自学约佔20%。
教师应该提供自学提纲并适当辅导。
同步編程的使用方法同步编程是一种常见的编程方法,用于管理并发程序中的线程同步。
它可以确保多个线程按照特定的顺序执行,避免出现竞态条件和数据不一致的问题。
在本文中,我们将介绍同步编程的基本概念和常用的同步方法,以及如何正确使用它们。
一、同步编程的基本概念同步编程是指为了保证多个线程之间的数据一致性和正确执行顺序而采取的编程方式。
在并发编程中,多个线程可能同时访问共享资源,如果不加以限制和管理,就会导致数据竞态(Data Race)和不可预料的结果。
同步编程的目标是通过合理的同步机制,使得多个线程按照一定的次序执行,并在需要时等待其他线程完成特定的操作。
这样可以避免数据竞态和并发问题,提高程序的稳定性和可靠性。
二、常用的同步方法1. 互斥锁(Mutex)互斥锁是一种最基本的同步机制,用于保护共享资源不被多个线程同时访问。
在临界区代码执行前,线程会尝试获取锁,如果成功获取到锁,则可以执行临界区代码,执行完毕后再释放锁。
如果获取锁失败,线程会被阻塞,直到锁被释放。
互斥锁可以解决多个线程同时访问共享资源的问题,但是它也可能导致线程互相等待,造成死锁的情况。
因此,在使用互斥锁时,需要注意避免死锁。
2. 信号量(Semaphore)信号量是一种用于控制并发访问的同步机制。
它通过一个计数器来管理资源的访问权限,当计数器大于零时,线程可以访问资源,当计数器减为零时,其他线程需要等待。
线程使用资源后,需要释放信号量,使得其他线程可以访问。
信号量还可以用于限制同时访问某个资源的线程数量。
3. 条件变量(Condition Variable)条件变量通常与互斥锁一起使用,用于线程间的等待与唤醒操作。
当线程需要等待某个条件成立时,它会调用条件变量的等待方法,并释放持有的互斥锁,从而让其他线程可以继续执行。
当条件满足时,线程会被唤醒,并重新获取互斥锁,继续执行。
条件变量可以避免线程忙等待的问题,提高线程的效率。
它在解决生产者-消费者问题、多线程任务分配等场景下非常有用。
临界区管理实现 本组组员:周琪皓,董泉伟,钟佳锋,张倬慎 0 引言
随着多处理机体系结构的演变和分布式与并行系统的发展,并发多任务的程序设计技术已愈来愈显得重要,多线程设计模式在这些技术的发展中起着重要作用。在现代操作系统中,利用进(线)程间的并发性实现程序中并发成分的并行执行,可大大提高系统的处理能力和效率,但也可能带来诸如执行结果的不确定性等不良现象,因此并发系统中处理好进(线)程间的互斥与同步就显得至关重要。C++语言中的多线程机制是解决线程间的互斥与同步问题的重要工具,其应用(如网络多媒体应用、工业自动化控制等)很广泛,很复杂且常易出错。因此在应用程序设计过程中,要考虑多个线程如何同步使用进程的共享资源,如何让一个线程与另一个线程协调合作,以免产生线程间的访问冲突。语言提供的多线程机制能有避免同一共享互斥资源被多个线程同时访问,维护数据的一致性、安全性。生产者/消费者问题可作为并发进程的同步和互斥问题的一个抽象模型,广泛应用于通信和控制系统中。本文基于C++语言中的多线程机制,实现操作系统中生产者/消费者问题,以助人们更好地透解同步概念及其实现方法。
1 课程设计目的
通过模拟操作者生产者经典问题的实现,以及关于信号量和互斥锁对于多线程的运用,深入理解操作系统中多线程同步法的理论知识, 加深对教材中的重要算法的理解。同时通过编程实现这些算法,更好地掌握操作系统的原理及实现方法,提高综合运用各专业课知识的能力。 2 课程设计题目和要求 2.1 课程设计题目 题目: 临界区管理实现. 2.2课程设计目的与要求 初始条件: 1.操作系统:Windows 2.程序设计语言:C++语言 3.有界缓冲区内设有20个存储单元,其初值为0。放入/取出的数据项按增序设定为1-20这20个整型数。
技术要求: 1、 生产者和消费者各有两个以上。多个生产者或
多个消费者之间须有共享对缓冲区进行操作 的函数代码。每个生产者和消费者对有界缓冲 区进行操作后,即时显示有界缓冲区的全部内 容,当前指针位置。 2、 编写多线程同步方法解决生产者-消费者的程 序,并完成对进程进行模拟同步和互斥的控制。 2 设计总体思路
2.1 多线程编程思想 编写Windows下的多线程程序,需要使用头文件pthread.h以及windows.h.在LINUX下进行多线程编程首先要用到CreateThread()这个函数.函数CreateThread()用来创建一个线程,它的原型为: HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to security attributes DWORD dwStackSize, // initial thread stack size LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function LPVOID lpParameter, // argument for new thread DWORD dwCreationFlags, // creation flags LPDWORD lpThreadId); // pointer to receive thread ID 第一个参数是指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,它被设为NULL。 第二个参数是用于新线程的初始堆栈大小,默认值为0。在任何情况下,Windows根据需要动态延长堆栈的大小。 第三个参数是指向线程函数的指标。函数名称没有限制,但是必须以下列形式声明: DWORD WINAPI ThreadProc (PVOID pParam) ; 第四个参数为传递给ThreadProc的参数。这样主线程和从属线程就可以共享数据。 第五个参数通常为0,但当建立的线程不马上执行时为旗标CREATE_SUSPENDED。线程将暂停直到呼叫ResumeThread来恢复线程的执行为止。 第六个参数是一个指标,指向接受执行绪ID值的变量。
2.1.1线程数据 在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据。它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。 ThreadHandle[0]=CreateThread(NULL,0,Producer,NULL,0,&producer1)其六个参数分别表示为安全设置,堆栈大小,入口函数,函数参数,启动选项,输出线程 ID,返回线程句柄。 2.1.2 互斥锁 互斥锁用来保证一段时间内只有一个线程在执行一段代码,必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的.函数mutex = CreateMutex(NULL,FALSE,NULL);用来生成一个互斥锁.NULL参数表明使用默认属性.如果需要声明特定属性的互斥锁,须调用函数 CreateMutex(NULL,FALSE,NULL) WaitForSingleObject(mutex,INFINITE)声明开始用互斥锁上锁,直至调用ReleaseMutex(mutex)为止,均被上锁, 即同一时间只能被一个线程调用执行.当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那么此线程被阻塞,即程序将等待到另一个线程释放此互斥锁.
2.1.3 信号量 信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数aitForSingleObject(empty,INFINITE)增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数WaitForSingleObject(full,INFINITE)减少信号量。 函数 ReleaseSemaphore(full,1,NULL)用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。函数ReleaseSemaphor()用来释放信号量。
2.2 设计原理 生产者线程和消费者线程共享同一个缓冲队列,生产者线程向缓冲区中写数据,消费者线程从缓冲区中取数据。但两者必须在使用缓冲队列资源时保持互斥,否则可能会导致在写入时产生数据覆盖,在读出时得到错误数据。因而要在程序中设置一个互斥锁或公用信号量,用于保证线程间的互斥执行。同时生产者线程和消费者线程必须保持同步关系,因为生产者线程的执行为消费者线程提供了需要的数据,是其执行的前提。反之,消费者线程的执行为生产者线程腾出了空闲的缓冲单元,为写数据提供了条件。即消费者线程执行的前提:缓冲队列中至少有一个单元有数据;生产者线程执行的前提:缓冲队列中至少有一个单元是空的。在设计过程中,利用信号量和wait 、signal原语操作来实现。如图1所示:
图1 生产者、消费者共享有界缓冲区 2.3 原语操作实现 The structure of the producer process
do { // 生产产品 wait (empty); wait (mutex); // 往Buffer中放入产品 signal (mutex); signal (full); } while (true); The structure of the consumer process
do { wait (full); wait (mutex); // 从Buffer中取出产品 signal (mutex); signal (empty); // 消费产品 } while (true);
3 开发环境与工具
系统平台:Windows环境 实现语言:C++语言 开发工具:Vs2012
4 概要设计
4.1 数据结构设计 通过分析课程设计要求,具体设计出如下数据结构: 1. int buffer[20]={0};//定义缓冲区空间大小 2.包含数据结构pthread_t 它记录一个线程的号,主要包括下面几个函数,完成不同的功能: ThreadHandle[0]=CreateThread(NULL,0,Producer,NULL,0,&producer1); //创建一个线程。 ExitThread(0); CloseHandle(ThreadHandle[0]);
//等待一个线程结束。 4.2 程序模块实现
4.2.1 生产者(Producer)模块 生产者线程向一缓冲区中写入数据,且写入缓冲区的数目不能超过缓冲区容量。当生产者产生出数据,需要将其存入缓冲区之前,首先检查缓冲区中是否有“空”存储单元,若缓冲区存储单元全部用完,则生产者必须阻塞等待,直到消费者取走一个存储单元的数据,唤醒它。若缓冲区内有“空”存储单元,生产者需要判断此时是否有别的生产者或消费者正在使用缓冲区,若是有,则阻塞等待,否则,获得缓冲区的使用权,将数据存入缓冲区,释放缓冲区的使用权,其流程图如图2所示: