简介为什么需要进程间同步下面通过两个例子来了解什么是进程同步,为什么需要进程同步(一)生产者-消费者问题问题描述:有一组生产者进程生产产品,并将这些产品提供给消费者进程进行消费,生产者进程和消费者进程可以并发执行。两者之间设置了一个有n个缓冲区的缓冲池。生产者进程需要把生产出来的产品放到缓冲区中,消费者进程可以从缓冲区中取出产品来消费生产消费的过程。当生产者生产一个产品时,缓冲区中的产品将+1。同理,如果消费者从buffer中消费一个产品,buffer中的bufferproduct会是-1,在生活中用这个模型是没有问题的(比如在手机厂,手机生产出来后流水线上,会放到仓库里,消费者会从仓库里拿出手机消费。生产者-消费者模型从宏观上看没有问题)以上模型没有从宏观上看有问题,但是从计算机微观上看就会有问题。在计算机中,这个缓冲区位于高速缓存或主存中。如果生产者或者消费者要操作里面的数据,分三步:取出数据放入寄存器register=countb,Register+1,CPU的寄存器中register=register+1表示生产者完成了一个产品c,将寄存器放回寄存器的三个步骤buffercount=register是我们操作buffer的三个必要步骤。我们可以把buffer看成一个仓库,register寄存器看成生产者的地方或者消费者的地方。乍一看,这个模型似乎没有问题。从生产者程序或者消费者程序上看就可以了。没问题,但是如果两者并发执行,可能会出错(假设为10),假设此时执行了生产者的第一步,即register=count,此时两者都是10,而然后执行生产者的第二步,register=register+1,此时register中的值为+1,则register=11,此时count=10,假设生产者程序和消费者程序是并发执行,那么第三步可能轮到消费者执行,那么假设此时在消费者的第一步,register=count,那么此时消费者进程中register的值为10,并且然后执行第四步,假设第四步执行到consumer的第二步,即register=register-1,此时consumer进程的register的值变为9,而in中的值缓存还是10,然后执行第五步,第五步假设即执行消费者的第三步count=register,即将register中的值写回buffer。此时buffer的值和consumer的register都是9,consumer的操作到这里就完成了。接下来是生产者的另一个操作。生产者寄存器写回缓冲区,那么刚才,生产者寄存器等于11,那么执行完这一步后,会再次把寄存器写回缓冲区,那么缓冲区中的值就变成了11,计数=注册。那么这个外表其实是有问题的。一开始buffer的值是10,但是在执行的过程中,进行了+1和-1操作,所以它的值应该还是10,但是租了之后就变成了11,说明数据是错误的。错误原因是两个进程并发执行。它们在轮流操作缓冲区,导致缓冲区中的数据不一致。这就是生产者消费者问题。让我们看一个实际执行的例子。下面是一个简单的程序,使用两个线程模拟生产者和消费者的过程:#include#include#include#include#includepthread_mutextmutex=PTHREADMUTEXINITIALIZER;intnum=0;//全局变量,初值为0void*producer(void*){//producerinttimes=10000000;while(times--){//生产者循环num+1多次,表示生产过程,消费者循环-1//pthreadmutex_lock(&??mutex);数+=1;//pthreadmutexunlock(&??mutex);}}void*comsumer(void*){//consumerinttimes=10000000;while(times--){//pthreadmutexlock(&??mutex);数-=1;//pthreadmutexunlock(&??mutex);}}//在main函数中创建两个线程来模拟两个进程,一个线程执行生产者的逻辑,另一个线程执行消费者的逻辑intmain(){printf("Startamainfunction.");pthread_t线程1,线程1;pthread_create(&thread1,NULL,&producer,空);pthread_create(&thread1,NULL,&comsumer,NULL);pthread_join(thread1,NULL);pthread_join(thread2,NULL);printf("在主函数中打印:num=%d\n",num);retuen0}因为生产者和消费者的循环次数相同,那么执行结果应该为0,那么实际执行结果呢?我们会发现没有,那么这就是生产者和消费者的问题。上例中的buffer和num是criticalresources(2)PhilosophersDiningProblem问题描述:有五位哲学家,他们的生活方式是交替进行的,在思考和吃饭的时候,哲学家们共用一张圆桌,坐在圆桌周围的五把椅子上。圆桌上放着五碗五筷。通常哲学家只会思考,当他们饿了时,他们会尝试将左右筷子靠近他们。只有当他们有两根筷子时,他们才能吃饭。吃完后,他们放下筷子继续思考。那么为什么这个过程需要进程同步呢?你可以想象当哲学家吃饭时会发生什么。假设此时某个哲学家饿了,他需要拿起左边的筷子和右边的筷子吃饭。这时候,第一步是拿起左边的筷子,第二步是拿起右边的筷子。如果此时他发现右边的筷子已经被拿走了,他就会等待右边的筷子被松开。筷子松开后,他会拿起右边的筷子开始吃饭。这就是哲学家吃饭时可能会面临的问题,而这样似乎也没什么问题。看一个极端的情况,假设这五个哲学家同时饿了,同时拿起左边的筷子,那么他们会发现右边的筷子已经被拿走了(可以类比上面的圆桌想象一下那么,这时候五个哲学家会等待自己右边的筷子被放开,而此时所有的筷子都已经被自己拿起了,所以他们会互相等待但是拿不到筷子,他们将拿不到筷子。它会释放它左边的筷子,所以五个哲学家会饿死。这是最极端的情况。上面是吃哲学家的问题。现在替换筷子有资源,哲学家有进程。这就是计算机进程所做的。面对问题,筷子是关键资源。总结一下以上两个问题的根本原因是什么?根本问题是:彼此不沟通第一个生产者-消费者问题,假设生产者通知消费者我生产完一块第二个哲学家就餐问题,假设哲学家对旁边的哲学家感兴趣说我要吃饭了,这个时候不会有问题的。因此得出结论需要进程间同步,那么进程间同步解决什么问题呢?1.协调多个进程间竞争资源的使用顺序。2.实现资源的有效利用和多个并发执行进程之间的相互协作。2、进程间同步原理临界资源:临界资源是指一些不能被多个进程或线程同时访问的共享资源。当一个进程使用临街资源时,其他进程必须等待占用进程根据操作系统的同步机制释放共享资源,然后才能重新竞争使用共享资源。为了有效地约束临界资源,提出了进程间同步的四个原则。idlegive-in:资源未被占用,允许使用busywait:资源被占用,请求进程等待limitedwaiting:保证有限的等待时间能够使用资源,避免其他等待进程死锁进程需要让出CPU,即进程从执行态变为阻塞态。这也是进程间同步保证CPU高效使用的方法:消息队列、共享存储、信号量。这些进程间同步的方法会在后面的文章中详细介绍3.线程同步从上一篇文章《进程管理之进程实体》我们知道一个进程可能有一个或多个线程,线程共享进程资源。那么现在有个问题,如果多个线程并发使用进程资源会怎样?其实上面提到的生产者消费者问题和哲学家就餐问题也会出现,所以我们得出结论,进程中的多线程也是需要同步的,因为进程中的线程会并发使用进程中的共享资源线程同步方式:Mutex:这是一种锁,确保多个线程可以互斥访问关键资源。读写锁:这是为了处理读多写少或者写多读少而发明的锁自旋锁。条件变量的这些方法也会在后面的文章中详细介绍。在瞬息万变的技术中寻找不变性,是一个技术人的核心竞争力。知行合一,理论与实践相结合