线程的同步与互斥(生产者与消费者模型)

一个进程中可以有多个线程,这些线程共享进程的资源,但当多个线程访问同一个资源时,在并不能保证操作是原子的情况下,就会产生冲突而使数据最终的结果不准确,像上次我们提到的将一个数进行加1操作需要三步:将数据从内存中取出;将数据加1;再将数据放回内存中,当多个线程并发执行这一操作时,有可能一个线程刚将数据从内存中取出就被临时切换出去了,这时别的线程进行加1操作,而当被切出去的线程重新回来继续执行加1操作时,这个数据已经改变了但是它拿到的还是原来的值,再进行加1放回内存时,结果就和我们所期望达到的不一样了。

再有一个栗子就是接下来要谈到的生产者与消费者,生产者生产产品,消费者消费产品,它们之间会访问到一个共同的临界资源那就是产品,既然是临界资源,那就不得不提到互斥了,当生产者正在生产产品的时候消费者不能中途打断取走产品,因此二者之间是有一个互斥的关系,需要有一个互斥量来维护,下面可以举个栗子:

可以用链表来作为二者“交易”的场所,生产者将生产出来的产品放入链表,而消费者从链表中取走所需要的产品:

首先要创建出链表的结点并有初始化;

生产者要向链表中放入数据,实现一个函数push_node,用头插的方式插入数据;而消费者要从链表中取出数据,实现一个函数pop_node,用尾取的方式取走数据;

因为二者的互斥关系,也为了观察方便两个线程函数都需要打印出数据会对输出设备产生访问冲突,需要加入互斥锁:

在上面的程序栗子中,生产者每隔一秒向链表头部插入一个随机数据,而消费者每隔两秒从链表的尾部取走数据,运行程序可得如下结果:

从上面的结果中不难看出,消费者取走的是链表尾部的结点数据,也就是按照“先进先出(FIFO)”的模式;而如果将生产者与消费者之间的时间颠倒一下,也就是生产者每隔一秒生产数据,而消费者每隔两秒取一个数据,则结果就会如下:

因此并不难知道,虽然加入了互斥量解决了线程之间访问临界资源的冲突问题,但似乎还并不是那么高效,也就是如果生产者生产速度比消费者消费产品的速度要快,则会有产品剩余;如果消费者比生产者生产产品的速度要快,则消费者会拿不到准确有效的数据;

因此,最好有一种解决办法,可以让消费者跟着生产者的步伐,生产者每生产出了一件产品,就通知消费者过来取数据,这样就提高了效率实现了线程之间的顺序性,也就是同步,可以加入条件变量的控制:

之所以将pop_node的结果判断放在consumer线程函数的一个while循环里,是因为有可能多次获取链尾数据都为无效值,则消费者就需要一直等待;

运行程序:

观察结果就如预期的那样,生产者和消费者之间有一个有序的过程,可以提高效率;

===========================================================================================

上面提到的栗子中,只有一个生产者和一个消费者,它们之间是同步与互斥的关系,那如果是多个生产者和多个消费者呢?

当一件产品由一个生产者生产时,其他的生产者就不能插手干预;同样,当一件产品被一个消费者给消费了时,别的消费者就不能再消费这件产品了;

因此,当有多个生产者和多个消费者时,生产者和消费者之间的关系是同步与互斥的,而生产者和生产者之间以及消费者和消费者之间的关系是互斥的:

栗子时间:

创建出多个生产者和多个消费者,生成者之间执行同一份线程函数,其实就是对同一个链表进行操作,而多个消费者之间也是执行同一个线程函数,从同一个链表中取函数,只是因为它们之间加入了互斥锁,因此彼此之间的操作都是互斥的,保证了操作的原子性,既不会有数据丢失,也不会有数据重复,运行程序:

从执行结果可以看出每一份数据都是独立有效的,并且生产者和消费者交互对链表进行操作,完成了线程之间的同步与互斥。

===========================================================================================

总结:

  1. 线程之间共享同一个进程的资源,因此也会对临界资源的访问产生冲突,需要加入互斥量来保证每个线程对临界资源操作的原子性;
  2. 线程间的互斥保证了对临界资源操作的原子性,但线程间的同步可以提高效率,使线程间的并发执行变得有序;
  3. 互斥锁和条件变量可以完成线程间的同步与互斥,使用互斥锁时需要申请,申请不到则线程挂起等待,使用完成释放锁,再重新竞争锁资源;使用条件变量时调用wait挂起等待的一方需要另一方来使其满足条件将其唤醒。

《完》

时间: 2024-10-08 21:52:07

线程的同步与互斥(生产者与消费者模型)的相关文章

线程的同步与互斥---生产者消费者模型

生产者与消费者模型 生产者与消费者模型是一种描述进程间同步与互斥的一个方式,在这个模式下有两类人,一个是不停产生数据的生产者,一个是不停获取数据的消费者,为了效率最高,就必须保持两者之间的同步与互斥. 为了在生产者与消费者使用mutex保持互斥的前提下,posix版本下还有另外一个函数cond它的作用是在生产者生产出来一个数据时便提醒消费者,而消费者在没东西消费时可以使用cond将自己保持在一个等待生产者信号和关闭锁的状态,当收到信号时将锁打开然后消费. 这是cond的创建与销毁:       

信号量生产者和消费者模型

使用信号量完成线程间同步,模拟生产者,消费者问题.                                         [sem_product_consumer.c] 思路分析: 规定: 如果□中有数据,生产者不能生产,只能阻塞. 如果□中没有数据,消费者不能消费,只能等待数据. 定义两个信号量:S满 = 0, S空 = 1 (S满代表满格的信号量,S空表示空格的信号量,程序起始,格子一定为空) -------------------------------------------

java的线程问题同步与互斥

以前学过java的线程,但是当时对Tread的理解不是很深,对于里面的同步与互斥,生产者与消费者问题,理解的不够深入,这次又从新学习java的多线程,感觉对线程的理解更加的深入了,觉得有必要写下博客记录下. 本文原创,转载请著明:http://blog.csdn.net/j903829182/article/details/38420503 1.java实现线程的方法: 1.实现Runnable接口,重写run方法,通过Thread的start方法启动线程.这种方法可以实现资源的共享 2.继承T

C# 线程(四):生产者和消费者

From : http://kb.cnblogs.com/page/42530/ 前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数.这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生. C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待.在C#中,关键字lock定义如下: lock(expres

线程的同步与互斥,死锁

线程的同步与互斥 多个线程同时访问共享数据时可能会发生冲突,比如两个线程同时把一个全局变量加1,结果可能不是我们所期待的: 我们看这段代码的执行结果: #include <stdio.h> #include <stdlib.h> #include <pthread.h> static int g_count=0; void *thread(void *arg) { int index=0; int tmp=0; while(index++<5000) { tmp=

Java线程学习整理--4---一个简单的生产者、消费者模型

 1.简单的小例子: 下面这个例子主要观察的是: 一个对象的wait()和notify()使用情况! 当一个对象调用了wait(),那么当前掌握该对象锁标记的线程,就会让出CPU的使用权,转而进入该对象的等待池中等待唤醒,这里说明一下,每一个对象都有一个独立的等待池和锁池! 等待池:上述的wait()后的线程会进入等待池中,处于下图线程声明周期(简单示意图) 中的这个状态,等待池中的线程任然具有对象的锁标记,但是处于休眠状态,不是可运行状态! 当该对象调用notify方法之后,就会在等待池中系统

线程概念及线程的同步与互斥

线程概念:它是运行在进程内部的的一个基本执行流,多线程的控制流程可以长期并存,一个进程中的数据段和代码段都是被该进程中的多个线程共享的,若定义一个函数,每个线程都可以调用,若定义一个全局变量,每个线程都可以访问. 线程还共享进程的以下内容:1.文件描述符表 2.当前的工作目录 3.用户id(uid)和组id(gid) 4.每种信号的处理方式. 但每个线程还必须有自己的私有部分:1.线程id 2.硬件上下文(硬件寄存器的值,栈指针等) 3.自己的栈空间(运行时的临时数据都要保存在自己的栈空间上)

开启子进程的两种方式,孤儿进程与僵尸进程,守护进程,互斥锁,IPC机制,生产者与消费者模型

开启子进程的两种方式 # # # 方式一: # from multiprocessing import Process # import time # # def task(x): # print('%s is running' %x) # time.sleep(3) # print('%s is done' %x) # # if __name__ == '__main__': # # Process(target=task,kwargs={'x':'子进程'}) # p=Process(tar

守护进程,互斥锁,IPC,队列,生产者与消费者模型

小知识点:在子进程中不能使用input输入! 一.守护进程 守护进程表示一个进程b 守护另一个进程a 当被守护的进程结束后,那么守护进程b也跟着结束了 应用场景:之所以开子进程,是为了帮助主进程完成某个任务,然而,如果主进程认为自己的事情一旦做完了就没有必要使用子进程了,就可以将子进程设置为守护进程 例如:在运行qq的过程,开启一个进程,用于下载文件,然而文件还没有下载完毕,qq就退出了,下载任务也应该跟随qq的退出而结束. from multiprocessing import Process

生产者、消费者模型

转载地址:http://blog.csdn.net/snow_5288/article/details/72794306 一.概念引入 日常生活中,每当我们缺少某些生活用品时,我们都会去超市进行购买,那么,你有没有想过,你是以什么身份去的超市呢?相信大部分人都会说自己是消费者,确实如此,那么既然我们是消费者,又是谁替我们生产各种各样的商品呢?当然是超市的各大供货商,自然而然地也就成了我们的生产者.如此一来,生产者有了,消费者也有了,那么将二者联系起来的超市又该作何理解呢?诚然,它本身是作为一座交