生产者消费者_第1页
生产者消费者_第2页
生产者消费者_第3页
生产者消费者_第4页
生产者消费者_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、生产者.消费者(producer-consumer)问题,也称作有界缓冲区(bounded-buffer)问题,两 个进程共享一个公共的固定大小的缓冲区。其中一个是生产者,用于将消息放入缓冲区; 另外一个是消费者,用于从缓冲区中取出消息。问题出现在当缓冲区已经满了,而此时生产 者还想向其中放入一个新的数据项的情形,其解决方法是让生产者此时进行休眠,等待消费 者从缓冲区中取走了一个或者多个数据后再去唤醒它。同样地,当缓冲区已经空了,而消费 者还想去取消息,此时也可以让消费者进行休眠,等待生产者放入一个或者多个数据时再唤 醒它。听起来好像蛮对的,无懈可击似的,但其实在实现时会有一个竞争条件存在的。

2、为了跟踪缓 冲区中的消息数目,需要一个变量counto如果缓冲区最多存放n个消息,则生产者的代 码会首先检查count是否达到n,如果是,则生产者休眠;否则,生产者向缓冲区中放入 一个消息,并增加count的值。/缓冲区大小#define n 100int count = 0;/*生产者进程*/ void procedure(void) (int item;while(true)消费者的代码也与此类似,首先检测count是否为0,如果是,则休眠;否则,从缓冲区 中取出消息并递减count的值。同时,每个进程也需要检查是否需要唤醒另一个进程。代 码可能如下:/跟踪缓冲区的记录数/缓冲区中的数据项

3、/无限循环/产生下一个数据项/如果缓冲区满了,进/将新数据项放入缓冲区/计数器加1/表明插入之前为空,/消/唤醒消费者item = producejtem();if (count = n)行休眠sleep();)insertjtem(item);count = count + 1;if (count = 1)费者等待wakeup(consumer);/*消费者进程*/void consumer(void)(int item;缓冲区中的数据项while(true)/ 无限循环if (count = 0)入休眠sleep();)item = removejtem();count = count -

4、1;if (count = n -1) 醒生产者wakeup(producer);)consumejtem(item);)/如果缓冲区为空,进/从缓冲区中取出一个数据项/计数器减1/缓冲区有空槽/唤/打印出数据项看上去很美,哪里出了问题,这里对count的访问是有可能出现竞争条件的:缓冲区为空, 消费者刚刚读取count的值为0,而此时调度程序决定暂停消费者并启动执行生产者。生 产者向缓冲区中加入一个数据项,count加1。现在count的值变成了 1.它推断刚才count 为0,所以此时消费者一定在休眠,于是生产者开始调用wakeup(consumer)来唤醒消费者。 但是,此时消费者在逻辑

5、上并没有休眠,所以wakeup信号就丢失了。当消费者下次运行 时,它将测试先前读到的count值,发现为0(注意,其实这个时刻count己经为1 了), 于是开始休眠(逻辑上)。而生产者下次运行的时候,count会继续递增,并且不会唤醒 consumer 了,所以迟早会填满缓冲区的,然后生产者也休眠,这样两个进程就都永远的休 眠下去了。1,使用信号量解决生产者-消费者问题首先了解一下信号量吧,信号量是e.w.dijkstra在1965年提出的一种方法,它是使用一个 整型变量来累计唤醒的次数,供以后使用。在他的建议中,引入了一个新的变量类型,称为 信号量(semaphore), 一个信号量的取值

6、可以为0 (表示没有保存下来的唤醒操作)或者为 正值(表示有一个或多个唤醒操作)。并且设立了两种操作:down和up (分别为一般化后的sleep和wakeup,其实也是一般 教科书上说的p/v向量)。对一个信号量执行down操作,表示检查其值是否大于0,如 果该值大于0,则将其值减1 (即用掉一个保存的唤醒信号)并继续;如果为0,则进程休 眠,而且此时down操作并未结束。另外,就是检查数值,修改变量值以及可能发生的休眠操作都作为单一的,不可分割的原子操作来完成。下面开始考虑用信号量来解决生产者-消费者问题了,不过在此之前,再次分析一下这个问 题的本质会更清晰点:问题的实质在于发给一个(尚)

7、未休眠进程(如上的消费者进程在只 判断了 count = 0后即被调度出来,还未休眠)的wakeup信号丢失(如上的生产者进程 在判断了 count = 1后以为消费者进程休眠,而唤醒它)了。如果它没有丢失,则一切都 会很好。#define n 100typedef int semaphore;semaphore mutex = 1;semaphore empty = n;semaphore full= 0;/缓冲区中的槽数目/信号量一般被定义为特殊的整型数据/控制对临界区的访问计数缓冲区中的空槽数目/计数缓冲区中的满槽数目/*生产者进程*/ void proceducer(void) (in

8、t item;while(l)item = procedure_item(); down(&empty); down(&mutex);insertjtem(item);up(&mutex);up(&full);加1)/生成数据/将空槽数目减1/进入临界区/将新数据放入缓冲区/离开临界区/将满槽的数目/*消费者进程*/ void consumer(voi) (int item;while(l)down(&full);down(&mutex);item = removejtem(); up(&mutex);/将满槽数目减1/进入临界区/从缓冲区中取出数据项/离开临界区up(&empty);/将空槽

9、数目consumer_item(item);/处理数据项该解决方案使用了三个信号量:一个为full,用来记录充满的缓冲槽的数目,一个为empty, 记录空的缓冲槽总数,一个为mutex,用来确保生产者和消费者不会同时访问缓冲区。mutex 的初始值为1,供两个或者多个进程使用的信号量,保证同一个时刻只有一个进程可以进入 临界区,称为二元信号量(binarysemaphore)。如果每一个进程在进入临界区前都执行一个 down(.),在刚刚退出临界区时执行一个up(.),就能够实现互斥。另外,通常是将down和up操作作为系统调用来实现,而且os只需要在执行以下操作 时暂时禁止全部中断:测试信号

10、量,更新信号量以及在需要时使某个进程休眠。这里使用了三个信号量,但是它们的目的却不相同,其中full和empty用来同步 (synchronization),而 mutex 用来实现互斥。2,使用消息传递解决生产者-消费者问题这种ipc方式使用两条原语send和receive,也是系统调用。如:send(dest, &msg) /将消息msg发送到目标(进程)dest中receive(src, &msg) /接收由src过来的msg,如果没有消息可用,则可能阻塞接收者消息传递系统会面临位于网络中不同机器上的通信进程的情形,所以会更加的更杂。如:消 息可能被网络丢失,一般使用确认(ack)消息。

11、如果发送方在一定的时间段内没有收到确 认消息,则重发消息。如果消息本身被正确接收,但是返回的ack消息丢失,发送方则重发消息,这样接收方就 会收到两份同样的消息。一般使用在每条原始消息的头部嵌入一个连续的序号来解决这个问 题。另外,消息传递系统还需要解决进程命名的问题,在send和receive系统调用中指定的进 程必须没有二义性的。还有其他的一些问题,如性能问题,身份认证等等,不过那个就会扯 多了,还是看看如果解决这个生产者-一消费者的问题吧:#define n 100/缓冲区中的槽数目/*生产者进程*/void proceducer(void)(int item;message msg;消

12、息缓冲区while(l)item = procedure_item(); receive(consumeo &msg); build_msg(&msg, item); send(consumec &msg);)/*消费者进程*/void consumer(voi)(int item, i;message msg;for(i=0; in; i+)send(producer; &msg);while(l)receive(producer, &msg); item = extract_item(&msg); send(proceduer, &msg);产者consumer_item(item);)/

13、生成数据/等待消费者发送空的缓冲区/创建待发送消息/发送数据项给消费者/发送给生产者n个空缓冲区/接收包含数据项的消息/解析消息,并组装成数据项/然后又将空缓冲区发送回生/处理数据项在这个解决方案中,共使用了 n条消息,有点类似于上一个的共享内存缓冲区的n个槽, 消费者进程这边首先通过一个for循环将n条空消息发送给生产者。当生产者向消费者传 递一个数据项时,是通过取走每一条接收到的空消息,然后送回填充了内容的消息给消费者 的。通过这种方式,整个消息传递系统中的总的消息数(包括空的消息+存了数据项的消 息=n)是不变的。如果运行过程中,生产者进程的速度比消费者快,则所有的消息最终都会塞满,然后

14、生产者 进程就会等待消费者(即使调用procedure也是阻塞在receive处),直到消费者返回一条 空的消息;反之亦然。下面再来看一下消息传递方式的两种变体。一种是:为每一个进程分配一个唯一的地址,让 消息按照这个进程的地址进行编址。也就是send和receive调用的第一个参数指定为具体 的进程地址。另一种是:引入信箱(mailbox,现在正在做的一个项目,就是这种方式),可 以信箱就像一个盒子,里面装了很多的信件,这个信件就是我们要传递的消息,当然信箱是 有容量限制的(现在yahoo好像推出无限容量的,呵呵)。当使用信箱时,send和receive 系统调用中的地址参数就是信箱的地址,

15、而不是进程的地址。当一个进程尝试向一个容量爆 满的信箱发送消息时,它将会被挂起,直到信箱中有消息被取走。多线程一,线程的一些基本知识。进程与线程所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中就是 一个进程,当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个 线程。进程(process)当一个程序进入内存运行即变成一个进程,进程处于运行过程中的程序,并且具有一定的 独立功能,进程是系统进行资源分配和调用的独立单位,进程切换开销大。多进程在操作系统中,能同时运行多个任务程序。进程包含三大特征1,独立性:进程是系统中独立存在的实体,它可以拥有自己独立的

16、资源,每一个进程都拥有自己私有 的地址空间,没有经过进程本身允许的情况下,一个用户不可以直接访问其他进程的地址空 间。2,动态性:进程与程序的区别在于程序只是个静态的指定集合,而进程是一个正在系统中活动的指定 集合,在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些状态 在程序中是不具备的。3,并发性:多个线程可以在单个处理器是并发执行多个进程,进程之间,不会互相影响。注:对于一个cpu而言,它在某个时间点上只能执行一个程序,就是说只能运行一个进程。cpu 不断在这些进程之间轮回切换,cpu的执行速度相对于我们的感觉太快,我们感觉不到,所 以cpu在多个进程之间轮回执行,但

17、我们人类感觉好像多个进程同时执行。线程(thread)线程被称为轻量级的进程,线程是进程的执行单元,就像进程在操作系统中的地位一样。 线程在程序中是独立的,并发的执行的流。当进程被初始化后,主线程就被创建了,一般的 应用程序来说,通常仅要求有一个主线程,但我们也可以在该进程内创建多条顺序执行流, 这些顺序执行流就是线程,每条线程是相互独立的。注:总而言之,一个程序运行后,至少有一个进程,一个进程里可以包含多个线程,但至少要 包含一个线程。多线程(multithreading)在同一个应用程序中,有多个顺序执行流同时执行。线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程将共

18、享同 一个进程的虚拟空间,-线程共享的环境包括进程代码段,进程的公有数据等,利用这些共 享的数据,线程很容易实现相互之间的通信。当操作系统创建一个进程时,该进程必须分配独立的内存空间,分配大量相关资料,但创 建一个线程简单的多,因此使用多线程来实现并发比使用多进程来实现并发的性能要高得多。多线程的优点1,进程之间不能共享内存,但线程之间共享内存非常容易。2,系统创建进程需要为该进程重新分配系统资源,但创建线程代价要小的多,因此使用多 线程来实现多个任务并发比多进程要效率高。3, java语言中内置多线程功能的支持,而不是单纯的作为底层的操作系统的调动方式,从 而简化了 java的多线程编程。二

19、,线程的启动和创建thread 类(java.iang.thread 类)在java语言中thread类代表线程,所有的线程对象都必须是thread类或其子类的实例。 每条线程的作用是完成一定的任务,实际上就是执行一段程序流,也叫一段顺序执行的代码, java使用run方法来封装这样一段程序流。继承thread类创建和启动线程1,定义thread类的子类,并重写该类的run方法,该run方法的方法体就是代表了线程 需要完成的任务,run方法也被称为线程执行体。2,创建thread类子类的实例,即创建了线程的对象。3,用线程对象的start方法来启动该线程。start。方法使该线程开始执行,ja

20、va虚拟机调用该线程的run方法。getname()方法返回该线程的名称setname(string name)方法修改该线程的名称interrupt();中断线程sleep(long millis);在指定的亳秒内让当前正在执行的线程休眠.1,实现runable接口的实现类,并重写该接i i的run方法,该run方法的方法体同样是该 线程的执行体。2,创建runable实现类的实例,并以此实例作为thread类的目标来创建thread类的目标创建thread对象,该thread对象才是真正的线程对象。thread类和runable接口创建线程的对比继承thread类方式的多线程的优点:编写简

21、单,如果访问当前线程,无需使用thread.currentthread方法,直接使用this,即可 获得当前线程。继承thread类方式的多线程缺点:因为线程已经继承了 thread类,所以不能再继承其他的类。实现runable接口的优点:线程只是实现了 runable接口,还可以继承其他的类,在这种方式下,可以多个线程共享 同一个目标(target)对象,所以非常适合多个线程来处理同一份资源的情况,从而可以将cpu 代码和数据分开,形成清晰的模型,体现了面向对象的思想。实现runable接口的缺点:编程稍微更杂,如果需要访问当前线程,必须使用thread.currentthread方法。三,

22、线程的生命周期当线程被创建并启动以后,它既不是一启动就进入了执行的状态,也不是一直处于执行状 态,在线程的生命周期里,它要经过新建,就绪,运行,阻塞,死亡五种状态。尤其是当线 程启动以后,它不能一直筋占着cpu独立运行,所以cpu多条线程之间切换,于是线程状 态也会多次在运行和阻塞之间切换。新建或就绪状态启动线程使用start方法,而不是run方法,永远不用调用线程对象的run方法,调用start 方法来启动线程系统会把该run方法当成线程执行体来处理。但如果直接调用线程对象的 run方法,则run方法立即会被执行,而且在run方法返回之前其他线程无法并发执行,也 就是说系统吧线程对象当成一个

23、普通的对象,而run方法也是一个普通方法,而不是线程执 行体。当线程对象调用了 start方法之后,该线程处于就绪状态,jvm会为创建方法调用栈和线程 计数器处于这个状态中的线程,并没有开始运行,它只是表示该线程可以运行了,至于该线 程何时开始运行,取决于jvm里线程调度器的调度。运行和阻塞状态如果处于就绪状态的线程获得了 cpu资源,开始执行run方法的执行体,则该线程处于运 行状态,当一条线程开始运行后,它不可能一直处于运行状态,线程在运行的过程中,需要 被中断,目的是使其他线程获得执行的机会,当发生以下情况下,线程将会进入阻塞状态;1,线程调用sleep方法主动放弃所占有的cpu资源。2

24、,线程调用一个阻塞式10方法,该方法返回之前该线程被阻塞。3,线程试图获得一个同步监视器,但该同步监视器正被其他线程所占据。4,线程在等待某个通知(ntifyntifyall方法 在objiet类里)。5,程序调用了线程的suspend方法,将线程挂起暂停),不过这个方法容易导致死锁,所 以程序尽量避免该方法。线程重新进入就绪状态有以卜.情况:1,调用sleep方法的线程经过指定的时间。2,线程调用阻塞式10方法已经返回。3,线程成功的获得了试图取得同步监视器。4,线程正在等待某个通知,其他线程发出了通知。5,处于挂起状态的线程被调用了 resume方法恢复。注:阻塞状态也叫不可运行状态调用y

25、ield方法可以让当前运行的线程转入就绪状态。线程会在以下三种方式之一结束线程,处于死亡状态:1, run方法执行完成,线程正常结束。2,线程抛出一个未捕获的exception或error。3,直接调用线程的stop方法来结束该线程,该方法容易导致死锁,通常不推荐用。isalive 方法测试某条线程是否已经死亡,当线程处于就绪,运行,阻塞三种状态时,该方法返回true, 当该线程处于创建,死亡两种状态时,该方法返回false。注:不要试图对一个已经死亡的线程调用start方法,使它重新启动,该线程将不可再次作为线 程执行,它会抛出川egathreadstateexception异常。不要让处于

26、死亡状态的线程调用start方法,程序只能对新建状态的线程调用start方法, 对新建状态的线程两次调用start方法也是错误的。join。方法等待被join的线程执行完成。join(long millis)方法等join的新车的时间最长为minllis亳秒,如果在millis亳秒内被join的线程还没有执行结 束,则不再等待。join(long millis jnt nanos)方法等待被join的时机最长为millis亳秒加nanos纳秒。setpriority()和方法:来设值和取值,返回线程的优先级,其中setpriority可以是个整数,范围是一到十之间,也可以是max_priori

27、ty , min_priority , n0rm_pri0rity 三个静态常量。getpriority()方法返回线程的优先级。每个线程执行时,都具有一定的优先级,优先级高的线程获得较多的 执行机会,而优先级低的线程则获得较小的执行机会。后台线程(daemonthread)有一种线程它是在后台运行的,它的任务是为其他线程提供服务,这种线程称为后台线程, 又称为守护线程,又称为精灵线程。jvm的垃圾回收线程就是典型的后台线程。后台线程有 个特征:如果所有的前台线程都死亡,后台线程会自动死亡。调用thread对象的 setdaemon(blooean on)方法(参数传true),可将指定线程设

28、置为后台线程,isdaemon()判断该 线程是否为后台线程,如果该线程是守护线程,则返回true;否则返回fa ise osleep(long millis)方法(线程睡眠)让当前正在执行的线程暂停并进入阻塞状态,该方法受到系统计时器和线程调度器的精度 和准确度的影响。注:当前线程调用sleep方法,进入阻塞状态后,在sleep时间段内,该线程不会获得执行的机 会,即使系统中没有其他可运行的线程,处于sleep时间段里的线程也不会运行。因此sleep 常用来暂停程序的执行,yield。方法(线程让步)yield方法和sleep有点相似的方法,让当前正在执行的线程暂停,但它不会阻塞该线程, 它

29、只是将该线程转入就绪状态,yield方法只是让当前线程暂停一下,让系统的线程调度器 从新调度一次,当某个线程调用了 yield方法暂停之后,只有优先级与当前线程相同,或者 优先级比当前线程更高的就绪状态的线程才会获得执行的机会sleep方法和yield方法的区别1, sleep方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级,但 yield方法只会给优先级相同或优先级更高的线程执行机会。2, sleep方法会将线程转入阻塞状态,直到经过阻塞时间,才会转入就绪状态,而yield方 法不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态,因此完全有可能某个线程 用yield方法

30、暂停之后立即再次获得处理器资源被执行。3, sleep方法声明抛出interruptedexception异常。所有调用sleep方法时,要么扑捉该异 常,要么显示声明抛出该异常。而yield方法则没有声明抛出任何异常。4, sleep方法比yield方法有更好的可移植性,通常不用依靠yield方法来控制并发线程的 执行。interrupt()使该线程中断,如果一个线程抛出异常,可以用interrupt在catch里中断该线程.thread()分配新的thread对象。 thread(runnable target)分配新的thread对象。 thread(runnable target, s

31、tring name)分配新的thread对象。thread(string name)static thread currentthread()返回对当前正在执行的线程对象的引用。string getname()返回该线程的名称。void setname(string name)改变线程名称,使之与参数name相同。int getpriority()返回线程的优先级。void setpriority(int newpriority)更改线程的优先级。void interrupt()中断线程。boolean isalive()测试线程是否处于活动状态。boolean isdaemon()测试该线程是否为守护线程。void join()等待该线程终止。void join(long millis)等待该线程终止的时间最长为millis亳秒。void run()如果该线程是使用独立的runnable运行对象构造的,则调用该runnable对 象的run方法;否则,该方法不执行任何操作并返回。void setdaemon(boolean on)将该线程标记为守护线程或用户线程。void start()使该线程开始执行:java虚拟机调用该线程的run方法。static void yield()暂停当前正在执行的线程对象,并执行其他线程。threadgroup 类threadg

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论