美文网首页操作系统
进程并发编程基础

进程并发编程基础

作者: madao756 | 来源:发表于2019-06-24 21:48 被阅读15次

    前言:看了操作系统并发编程的基础,做个笔记并用 C 实现常见的一种并发编程的模型——消费者、生产者模型

    进程之间的关系

    第一种是竞争关系

    系统中的多个进程之间彼此无关,它们并不知道其他进程的存在,并且也不受其他进程执行的影响。因而, 必然要出现多个进程竞争资源的问题。当多个进程竞争共享硬设备、存储器、处理器 和文件等资源时,操作系统必须协调好进程对资源的争用

    进程的互斥(mutual exclusion )是解决进程间竞争关系(间接制约关系)的手段。 进程互斥指若干个进程要使用同一共享资源时,任何时刻最多允许一个进程去使用,其他要使用该资源的进程必须等待,直到占有资源的进程释放该资源。

    第二种是协作关系

    某些进程为完成同一任务需要分工协作,由于合作的每一个进程都是独立地以不可预知的速度推进,这就需要相互协作的进程在某些协调点上协 调各自的工作。

    进程的同步(Synchronization)是解决进程间协作关系(直接制约关系)的手段。进程同步指两个以上进程基于某个条件来协调它们的活动。一个进程的执行依赖于另一个协作进程的消息或信号,当一个进程没有得到来自于另一个进程的消息或信号时则需等待,直到消息或信号到达才被唤醒。

    不难看出,进程互斥关系是一种特殊的进程同步关系,即逐次使用互斥共享资源,也是对进程使用资源次序上的一种协调

    如何实现互斥

    无论是竞争还是协作,都必须实现互斥。

    而实现互斥的关键在于:进程 A 正在使用的临界资源,哪怕在使用的过程中被中断了,其他进程的临界区都不能动进程 A 的资源。

    硬件方式实现互斥

    用硬件的方式增加一条汇编指令,比如 TestAndSet

    伪代码如下

    // 主程序
    global lock = false
    
        
    
    repeate {nothing} until TestAndSet(&lock)
    // 临界区代码
    // do someting
    // 临界区结束
    lock = false
    

    这里的 TestAndSet 使用汇编指令实现的,也就是他不能被中断。lock 是全局变量,所有的进程都能访问到

    实现的功能就是:检查 lock 是否是 false,如果是 false 就把 lock 设为 true。如果 lock 是 false。就不会执行repeate {nothing}

    有的进程执行的时候,lock 是 true。就会一直执行 repeate {nothing},直到 CPU 时间片结束,结束调用。这样就实现了互斥。

    但是,这样有一个很大的缺点就是忙等,我们不想要忙等,我们想要 lock = true 的时候进程被阻塞,然后等待调用

    用信号量(semaphore)实现互斥 重点!

    semaphore 最重要的是两个函数:

    • wait(semaphore* s)

    • signal(semaphore* s)

    这是系统给的原语。原语和其他函数最大的区别就是:它虽然也是一系列指令构成,但是在执行的过程中不可中断

    wait 和 signal 的伪代码如下:

    wait(semaphore* s) {
        if(s.count-- < 0) {
            // 阻塞
        }
    }
    
    signal(semaphore* s) {
        if(s.count++ <= 0) {
            // 调用某个被阻塞的进程
            call();
        }
    }
    

    那怎么用这两个函数实现互斥呢?我们在访问临界资源的时候会先执行 wait 函数如果 (s.count-- < 0)。则被阻塞,而不是忙等。否则就执行临界区代码(资源够用,不需要阻塞)。

    而执行完临界区以后,会执行 signal 函数。如果 (s.count++ <= 0) 。则去唤醒一个被阻塞的函数,否则啥也不做,仅仅加一(没有被阻塞的进程)。

    通过一个实例来理解这一个过程

    #include <stdio.h>
    #include <stdlib.h>
    #include <semaphore.h>
    #include <unistd.h>
    
    
    sem_t s;
    
    
    int main(int argc, char const *argv[])
    {
        int value = 0;
    
        sem_init(&s, 0, 10);
    
        sem_getvalue(&s, &value);
        printf("initialization s.count = %d\n", value);
    
        // wait
        sem_wait(&s);
        sem_getvalue(&s, &value);
        printf("after wait s.count = %d\n", value);
    
        // signal
        sem_post(&s);
        sem_getvalue(&s, &value);
        printf("after signal s.count = %d\n", value);
    
        
        sem_destroy(&s);
    
        return 0;
    }
    
    

    输出:

    initialization s.count = 10
    after wait s.count = 9
    after signal s.count = 10
    

    C 实例

    我们通过两个经典模型,来深入理解进程并发编程

    消费者、生产者模型

    该问题描述了共享固定大小缓冲区的两个进程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

    #include <stdio.h>
    #include <stdlib.h>
    #include <semaphore.h>
    #include <unistd.h>
    
    #define SIZE 1 /*buffer size*/
    
    
    
    sem_t empty;
    sem_t full;
    sem_t mutex;
    
    int buffer[SIZE] = {0};
    
    void producer(int idx, int value) {
        sem_wait(&full);
        sem_wait(&mutex);
    
        buffer[idx] = value;
    
        sem_post(&mutex);
        sem_post(&empty);
    }
    
    void consumer(int idx) {
    
        sem_wait(&empty);
        sem_wait(&mutex);
    
        printf("从 buffer[%d] 取出 %d \n", idx, buffer[idx]);
        buffer[idx] = -1;
    
        sem_post(&mutex);
        sem_post(&full);
    }
    
    int main(int argc, char const *argv[]) {
        int value;
    
    
        // 多个生产者和消费者,只有一个进程能够拥有 buffer,这是所有进程的互斥
        sem_init(&mutex, 0, 1);
    
    
        // 生产者在满的时候不能生产
        sem_init(&full, 0, SIZE);
    
        // 消费者不能在空的时候消费
        sem_init(&empty, 0, 0);
    
        // 假设有四个进程  
        for(int i = 0; i < 4; i++) {
            int p = fork();
    
    
            if(p == 0) {
    
                switch (i)
                {
                    case 0:
                        producer(0, 10);
                        break;
                    case 1:
                        producer(0, 100);
                        break;
                    case 2:
                        consumer(0);
                        break;
                    case 3:
                        consumer(0);
                        break;
                    default :
                        // producer(0, 10);
                        break;
                }
    
            } else {
                printf("我是主进程:%d \n", p);
            }
    
            
        }
    
        sem_destroy(&mutex);
        sem_destroy(&empty);
        sem_destroy(&full);
    
        return 0;
    }
    

    相关文章

      网友评论

        本文标题:进程并发编程基础

        本文链接:https://www.haomeiwen.com/subject/uypaqctx.html