进程间的通信--------操作系统
来源:互联网 发布:鼠标垫你们的知乎 编辑:程序博客网 时间:2024/05/21 17:15
一、要解决的问题:
- 如何把信息由一个进程传递给另外一个进程
- 如何处理两个或多个进程在临界区的问题
- 两个进程或多个进程的顺序问题(如何实现同步)
二、基本概念:
2.1竞争条件: 两个或多个进程读写某些共享数据时,最后的结果取决于进程运程运行的精确时序。(说到底,是访问临界资源时,a进程先访问一半时,a进程被阻塞,b进程去运行,也到了临界资源,把a的之前已经运行了的半个临界资源数据覆盖了,则下一次a在运行时,根据先前的临界区的值,就有了错误的执行结果)
2.2临界区: 为了避免竞争条件,也就是互斥访问共享资源(一个进程在使用另一个进程不得使用)。而临界区是程序片段,什么样的程序片呢?访问共享内存的程序片段。互斥即:是两个进程不同时在临区
避免竞争条件的四个条件:
- 任何两个进程不的同时处于临界区(竞争条件)
- 不应对cpu的速度和数量做任何假设(这个我自己理解为通过程序代码和运算速度,人可以猜测进入临界区的时间,让另外一个进程等待完这个时间后手动进入临界区,但这样是不可以的,必须把进入临界区的时间交给操作系统管理)
- 临界区外的进程不的阻塞其他进程(没有共享资源的约束,进程间不得以任何方式阻塞对方)
- 不的使进程无限期等待进入临界区(进程不能无限被阻塞)
三、忙等待互斥(几种互斥):
3.1屏蔽中断:单个处理器,每个进程刚进入临界区就把中断屏蔽了,在离开时打开。由于时钟中断屏蔽了,cpu不会发生进程间的切换,那么这个进程可以一直使用cpu,临界区。
问题:把中断控制权交给用户不安全。另一点是多处理器,一个cpu屏蔽中断,另外一个cpu是不会被屏蔽的。
3.2锁变量:设置一个共享锁变量,开始为零,进入为改变1,出来改变为0。这个一听就有问题,用一个共享变量去保护另一个共享资源,那么这个共享变量怎么保护?所以这个互斥是不行的。
3.3严格轮换法:这个也有一个变量turn(初始0),用于记录轮到哪一个进入临界区了。一个进程是当是0的时候进入,另一个是1的时候进入,当一个进程进入,另外一个进程不停的循环检查turn的值,知道turn改变才进入,这个循环检查就是忙等待,极为浪费cpu时间,只有在等待时间很短的时候,才用忙等待,当一个进程频繁进入临界区,另一个进程不频繁时,用严格轮换法,两个进程你一次我一次的进入,这样进入临界区频繁的进程就会被其他围在临界区的进程阻塞,违反了之前的条件。
3.4Peterson解法:
#define N 2//两个进程int turn;int interested[N];void enter_region(int process){ int other; other=1-process; interseted[process]=TRUE; turn=process; while(turn==process && interseted[other] == TRUE);}void leave_region(int process){ interested[process]=FALSE;}
turn表示轮到谁进入临界区了,数组代表了那一个进程想进入临界区。只有另外一个进程不想进临界区,我就能进入(interseted[other]==FALSE)或者轮到我的时候另外一个进程也想进入临界区时,我先进入而另外一个在等待我出来(turn==1-process)(解决了两个几乎同时进入的情况)。
3.5TSL指令: 硬件实现。TSL RX,LOCK语句,测试并加锁,将一个内存字lock读入寄存器RX,猴后再lock上存一个非零值,读写不可分割:该指令结束前其他处理器不可以访问内存字,采用锁住总线的方式(与屏蔽中断不同)。前面有点啰嗦,简单点说:同锁变量类似,一个共享变量lock存入register中,register的值为零则可进入,为1不可进入,忙等待检测是否为0,另一个进入操作结束时,变为0;
enter_region: TSL REGISTER,LOCK CMP REGISTER,#0 //锁是0吗 JNE enter_region //不是,则有进程进入临界区,忙等待 RETleave_region: MOVE LOCK,#0 RET
四、非忙等待的进程间的互斥:
忙等待的缺点:不允许进入临界区的进程原地等待,首先肯定会浪费cpu时间,其次,会有优优先级反转问题**:两个进程H(高优先级),L(低优先级),L在临界区,H处于就绪态运行,处于忙等待,H处于运行态,L不会被调度,无法离开临界区,H永远忙等待。
现在详细说一下优先级反转情况:①高优先级处于忙等待,低优先级无法执行,离开临界区。(就是上面说的) ②另一种是,高优先级阻塞,要等低优先级去触发实践唤醒,但低的运行时间少,搞得要等低的释放资源,若又处于中间优先级的(无共享资源)获得cpu时间反而多,高的就无法正常按规则执行了。
4.1 睡眠与唤醒; 把要忙等待的进程阻塞,让另外离开临界区的进程去唤醒他,这样就可以避免忙等待了。
这里引出了经典问题:生产者与消费者 ,单用睡眠与唤醒是无法解决这个问题的,可能会产生唤醒信号丢失,即使引入新变量来解决两个进程间的,还是没有从根本上解决问题。
4.2信号量: 使用一个信号量来累计唤醒次数(大于等于0)。并对信号量定义了两种操作down和up(原来他的论文中是PV操作,只是名字不一样)。
down: if(semaphore>0) semaphore--; else sleep(process);up: if(semaphore==0) wakeup(everySleepOnThisSemaphoreProcess); else semaphore++;
down和up都是原子操作(即一组关联操作要么都不执行,要么执行时不可被打断),还有一点很清楚:不会有那一个进程由于up操作而阻塞的
用信号量解决生产者消费者问题:
三个信号量:full(记录缓冲区槽数被填充了的数量)
empty(记录空槽数)
mutex(不会同时访问临界区)
我们用两种方式去使用这三个信号量的:mutex适用于互斥访问(二元信号量),full与empty用于实现同步:保证某种事件的顺序发生货不发生。
#define N 100 //缓冲区槽数typedef int semaphore;semaphore mutex=1;semaphore empty=N;semaphore full=0;void producer(void){ int item; while(TRUE) { item=producer_item(); down(&empty);//这两句若颠倒,则有问题。先进入缓冲区,发现不能生产了,堵住了,出不去 down(&mutex);//生产者消费者都堵塞了,这里为什么是empty?down是减,生产有空的是要减空的 insert_item(item); up(&mutex);//up操作不阻塞,无所谓先后 up(&full);//这里是full?生产一个加一个,可以消费了,up去唤醒消费者 }}void consumer(void){ int item; while(TRUE) { down(&full); down(&mutex); item=remove_item(); up(&mutex); up(&empty); consume_item(item); }}
这里实现很清楚了。理解信号量的思想,是由原子操作这个概念,相信它会在关键地方产生奇效。
4.3 互斥量
信号量是有计数能力的,而互斥量是信号量的简化版本,互斥量只适用于管理共享资源或一小段代码,由于互斥量在实现上很容易,互斥量在用户空间县城包时使用。
互斥量是一个二元信号量,有两种状态:解锁和加锁。
mutex_lock: TSL REGISTER,MUTEX //将互斥量复制到寄存器,并且将互斥量置为1 CMP REGISTER,#0 //互斥量是0? JZE ok //是 结束 CALL thread_yield //稍后再试 JMP MUTEX_lock //返回调用者,进入临界区ok: RET mutex_unlock: MOVE MUTEX,#0 RET
这与之前的TSL的enter_region进入临界区失败时,处于忙等待状态。这样有时钟存在,始终会调用其他进程。而在用户空间中的线程中,没有时钟中断存在,此线程永远霸占cpu
Pthraed中的互斥:
之前说过一些Pthread线程包关于线程创建销毁的一些函数,这里说与一下关于互斥量的Pthraed用。
- pthraed_mutex_init 创建一个互斥量
- pthread_mutex_destroy 撤销一个已经存在的信号量
- pthraed_mutex_lock 获得一个锁或阻塞
- pthraed_mutex_trylock 获得一个锁或失败
- pthraed_mutex_unlock 释放一个锁
Pthraed中的同步:
为达到的条件而阻塞
pthread_cond_init 创建一个条件变量
- pthread_cond_init 创建一个条件变量
- pthread_cond_destroy 撤销一个条件变量
- pthread_cond_wait 阻塞以等待一个信号
- pthraed_cond_signal 向另一个线程发信号唤醒它
- pthraed_cond_broadcast 想多个线程发信号让他都唤醒
例子:生产者发现空槽没有了,阻塞起来。当发现缓冲区满时,需要一个条件变量阻塞,不然再生产一个,存哪?没得存了,只得等待消费者通过条件变量唤醒
条件变量要和互斥量相联结,以避免出现条件竞争--一个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程这里写代码片待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件
#include<pthread.h>#include<stdio.h>#include<stdlib.h>#define MAX 1000pthread_mutex_t the_mutex;//互斥量pthread_cond_t condc,condp;//条件变量int buffer=0;//缓冲内容void *producer(void *ptr){ for(int i=1;i<=MAX;i++) { pthread_mutex_lock(&the_mutex);//互斥使用缓冲区 while(buffer!=0) { pthread_cond_wait(&condp,&the_mutex);//阻塞以等待信号 } buffer=i; printf("producer %d\n",i); pthread_cond_signal(&condc);//向另一个进程发出信号唤醒 pthread_mutex_unlock(&the_mutex);//释放缓冲区 } pthread_exit(0);}void *consumer(void *ptr){ int i; for(i=1;i<=MAX;i++) { pthread_mutex_lock(&the_mutex); while(buffer==0) pthread_cond_wait(&condc,&the_mutex); printf("consumer %d\n",buffer); buffer=0; pthread_cond_signal(&condp); pthread_mutex_unlock(&the_mutex); } pthread_exit(0);}int main(){ pthread_t pro,con;//线程号 pthread_mutex_init(&the_mutex,0);//创建一个互斥变量 pthread_cond_init(&condc,0);//创建一个条件变量 pthread_cond_init(&condp,0); pthread_create(&con,0,consumer,NULL);//创建线程 pthread_create(&pro,0,producer,NULL); pthread_join(pro,0);//如果没有pthread_join;主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了 pthread_join(con,0);//加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行 //pthread_join函数会让主线程阻塞,直到所有线程都已经退出 pthread_cond_destroy(&condc);//销毁条件变量 pthread_cond_destroy(&condp); pthread_mutex_destroy(&the_mutex);//销毁互斥变量}
上面的代码是书上的,我自己还没有编写过多线程的程序。不过看到这段代码我有一个疑问:在消费者首先互斥使用缓冲区(只有一个字大小)时,buffer是0,阻塞了,那么生产者无法进入缓冲区,然后无法唤醒消费者,这就不对了。我大约是相信书的内容不会轻易出逻辑错误,并且我对pthraed_cond_wait()函数不熟悉,我想问题出在这里,查了一下,发现如下:
pthread_cond_wait(&condc,&the_mutex);
//pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的the_mutex,(这里解决了问题)
//然后阻塞在等待队列里休眠,直到再次被唤醒
//(大多数情况下是等待的条件成立而被唤醒,唤醒后,
//该进程会先锁定先pthread_mutex_lock(&the_mutex);,
//再读取资源用这个流程是比较清楚的
这里为什么调用pthraed_cond_wait()来解除the_mutex互斥量呢?
这里引用一下别人的代码看看: [不然转载,贴一个连接](http://blog.csdn.net/bolike/article/details/9025389) 一个线程完成一项任务(涉及共享资源) 另一个线程需要共享资源达到一定条件时才能触发下一步,这样这个线程需要不断地判断到底达到条件没?浪费了cpu时忙 (上面的互斥量使用只是不让两个线程同时进入临界区,但是忙等待是没法解决的,而sleep()的话无法知道到底需要多长时间,不可行) 此时有条件变量的话会好很多,不用忙等待了。
4.4管程 (这里先略了)
4.5消息传递
如果一个分布式系统有多个cpu,并且每个cpu有自己的私有内存,之前的原语全都失效了,因为这些原语未提供机器 间的信息交互,所以引 入了消息 。
两条系统调用的原语:send和receive
send(destination,&message) //给定一个目标发送消息
receive(sourse,&message) //从消息源接信息
设计要点: 在网络上通信进程,消息可能丢失,为了反之丢失,双方达成协议,接收方在 接受到消息后,回送一个确认消息 ,如果发送方一段时间间隔未收到确认消息,则重发消息 若确认消息丢失,发送者有发送一次,,接收者需要判断是一个新消息还是重发的就消息很重要,一般是在原始消息中嵌入一个连续序号。 进程命名必须没有二义性,身份验证也需要解决 用消息传递解决生产者消费者问题:
#define N 100 void producer(void){ int item; message m; while(TRUE) { item=produce_item();//生产 receive(consumer,&m);//接受消费者发送的空缓冲区 build_message(&m.item);//建立新消息 send(consumer,&m);//发送 }}void consumer(void){ int item,i; message m; for(i=0;i<M;i++)//发送所有空消息 send(prosucer,&m); while(TRUE) { receive(producer,&m);//接受一个消息 item=extract_item(&m);//处理消息,缓冲区产生一个新的空消息 send(producer,&m);//发送空消息 consume_item(item);//处理 }}
如何存消息:
使用信箱,消费者生产者各有一个,这里信箱的大小是缓冲区的大小,心想可以容纳哪些已经被发送未被目标进程接收的消息 ,当成一个消息缓冲区。
4.6屏障
同步机制:一个进程组必须同时可以开始下一个阶段,不然都不可以进入。可以在每一个阶段尾设置一个屏障,先到达的被拦截,全部都到达了,才进入下一阶段。
- 进程间的通信--------操作系统
- [操作系统] 进程间的通信
- 操作系统:进程间通信
- 操作系统进程间通信
- 【操作系统】进程间通信
- 操作系统------进程间通信
- 操作系统进程间通信的方式
- 操作系统 - 进程间的通信方式:
- 操作系统进程间通信的方式
- 【操作系统】进程间通信的方式
- 【现代操作系统】进程的通信
- 【操作系统】进程的通信方式
- 操作系统---进程/线程 间通信
- 操作系统---进程/线程 间通信
- 操作系统 -- 进程间通信机制
- 一个操作系统的实现(10):进程间通信
- 操作系统(十四)------进程间通信的几种方式
- 操作系统--进程间的通信,同步和互斥等
- mongoDB初认识
- 561. Array Partition I
- java并发之ReentrantLock
- TCP-IP详解卷-TCP连接的建立与终结
- 实现TreeSet排序和遍历map的方法
- 进程间的通信--------操作系统
- jQuery 4 事件和动画
- Jmeter CSVDataSet配置器修改(可自定义用例开始行号)
- 学习React阶段性总结
- graphviz 安装和入门
- 使用位运算处理权限问题
- 《木兰辞》
- C/C++标准库中定义的各个类型最大值最小值limit.h
- jquery sortable组件配置参数中文注释及demo