进程间的通信--------操作系统

来源:互联网 发布:鼠标垫你们的知乎 编辑:程序博客网 时间: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屏障
同步机制:一个进程组必须同时可以开始下一个阶段,不然都不可以进入。可以在每一个阶段尾设置一个屏障,先到达的被拦截,全部都到达了,才进入下一阶段。

0 0
原创粉丝点击