PV操作原理解析

  • Post author:
  • Post category:其他




1 进程线程同步互斥介绍



1.1 进程同步

指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。属于直接制约关系。



1.2 进程互斥

当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一个进程才允许去访问此临界资源。属于间接制约关系。



1.3 需要执行互斥的场景

两个或两个以上的进程,同时进入进程共享变量的临界区域,需要执行进程互斥,否则将发生错误。例如:金科南区的ATM机只有一台,此时来了学生a和学生b,学生a先到,几秒钟之后也到了,这时b就只能进行等待。



1.4 需要同步的场景

在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系,为了协调进程之间的相互制约关系,此时需执行进程同步。例如:在前端vue开发中,需要执行c获取所有数据函数和渲染已获得数据前端显示,如果没有先执行获得所有数据函数,那么前端页面就无法渲染数据。



2 信号量介绍及应用



2.1 信号量概念

信号量是用于提供不同进程或者不同线程之间的同步手段的原语。信号量的数据结构是一个值和一个指针,指针指向等待该信号的下一个进程。信号量的值与相应的共享资源关联。当信号量中的值等于零时,该值表示当前可用资源的数量,当信号量的值小于零时,其绝对值表示当前阻塞队列中等待该进程的数量。

信号量分为内核信号量和用户进程信号量。

用户信号量中又分为System V信号量与Posix信号量。



2.2 PV操作概念

P操作为 ①将信号量S的值减1,即S=S-1 ②如果S大于等于0,则该进程继续执行,否则该进程进入等待队列。

S操作为 ①将信号量S的值加1,即S=S+1 ②如果S大于0,则该进程继续执行,否则释放等待队列中第一个等待信号量的进程。

信号量机制的原理。用可用资源的数量初始化信号量变量。之后,每当一个进程需要一些资源时,就会调用wait()函数,并将信号量变量的值减一。然后进程使用资源,在使用资源后,调用signal()函数,信号量变量的值加一。因此,当信号量变量的值变为 0 即所有资源都被进程占用并且没有资源可用时,如果其他进程想要使用资源,则该进程必须等待轮到它。这样,我们就实现了进程同步。



2.3 信号量应用 – 生产者消费者问题

场景:生产者生产产品,消费者进行购买,如现有商品少于5则不允许出售,初始商品数为0,共有两个生产者,六个消费者。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define CONSUMER_NUM 6
#define PRODUCER_NUM 2
pthread_t pids[CONSUMER_NUM+PRODUCER_NUM];
int ready = 0;
pthread_mutex_t mutex;
pthread_cond_t has_product;
void* producer(void* arg){
    int no = (int)arg;
    for(;;){
        pthread_mutex_lock(&mutex);
        ready++;
        printf("producer %d produce product \n",no);
        printf("now total product is %d\n",ready);
        if(ready >= 5){
            pthread_cond_signal(&has_product);
            printf("producer %d signal \n",no);
            pthread_mutex_unlock(&mutex);
        }
        pthread_mutex_unlock(&mutex);
        sleep(1);  
    }
}
void* consumer(void* arg){
    int num = (int)arg;
    
    for(;;){
        pthread_mutex_lock(&mutex);
        while(ready < 5){
            printf("consumer %d wait \n");
            pthread_cond_wait(&has_product,&mutex);
        }
        ready--;
        printf("consumer %d consume product \n",num);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}
void main(){
    pthread_mutex_init(&mutex,NULL);
    pthread_cond_init(&has_product,NULL);
    int i;
    for(i=0;i<PRODUCER_NUM;i++){
        pthread_create(&pids[i],NULL,producer,(void*)i);
    }
    for(i=0;i<CONSUMER_NUM;i++){
        pthread_create(&pids[PRODUCER_NUM+i],NULL,consumer,(void*)i);
    }
    sleep(10);
    for(i=0;i<CONSUMER_NUM+PRODUCER_NUM;i++){
        pthread_join(pids[i],NULL);
    }
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&has_product);
}

在这里插入图片描述

代码讲解:

pthread_t 创建线程,这里创建的线程数量是我规定的producer+consumer

pthread_mutex_t mutex在使用互斥锁之前,需要先创建一个互斥锁的对象。 互斥锁的类型是 pthread_mutex_t ,所以定义一个变量就是创建了一个互斥锁。

pthread_cond_t has_product 一般pthread_cond_t,会搭配pthread_mutex_t 一起使用的, 因为线程间通信时操作共享内存时,需要用到锁。当锁住的共享变量发生改变时,可能需要通知相应的线程(因为可能该共享变量涉及到多个线程),这时就需要用到pthread_cond_t这种条件变量来精准的通知某个或几个线程, 让他们执行相应的操作

pthread_cond_t涉及两个函数,一个是pthread_cond_signal函数,它在一个线程中,用来发送信号。一个是pthread_cond_wait函数,他在另一个线程中,用来接收信号。当线程执行到pthread_cond_wait函数时,他会释放相应的锁,让其他线程获得锁继续执行,这样其他线程才有机会给他发信号;当它接收到信号时,会重新去获得锁,如果没有获得锁,就阻塞等待,直到获得锁,才执行接收信号的相应操作。

pthread_mutex_lock(&mutex) pthread_mutex_lock是上锁操作,当 pthread_mutex_lock() 返回时,该互斥锁已被锁定。调用线程是该互斥锁的属主。如果该互斥锁已被另一个线程锁定和拥有,则调用线程将阻塞,直到该互斥锁变为可用为止。

生产者代码,由于产品至少需要五个才可以销售,生产者执行时,首先总量ready加一,然后判断是否够五个,如果够五个, pthread_cond_signal(&has_product),通知消费者,等待中的消费者线程可以执行,如果不够则不通知。

消费者代码,如果产品不充足(即低于五个),则消费者进入堵塞线程printf(“consumer %d wait \n”),pthread_cond_wait(&has_product,&mutex),开始等待。如果产品充足,则总量ready减一,消费者消费,然后释放锁pthread_mutex_unlock(&mutex)。

主函数代码,pthread_mutex_init(&mutex,NULL), pthread_cond_init(&has_product,NULL),在此之前已经定义了一个互斥锁,但是如果想使用这个互斥锁还是不行的,我们还需要对这个互斥锁进行初始化,使用函数 pthread_mutex_init() 对互斥锁进行初始化操作。pthread_mutex_init() 中第二个参数是 NULL 的话,互斥锁的属性会设置为默认属性,即快速互斥锁。

函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:

extern int pthread_cond_init __P ((pthread_cond_t

__cond,__const pthread_condattr_t

_

cond_attr)),其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。

结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD

PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用;如果选择为PTHREAD_PROCESS_SHARED则为多个进程间各线程公用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。

两个for循环,创建规定数量的线程

pthread_create(&pids[i],NULL,producer,(void

)i) 函数声明:int pthread_create(pthread_t

restrict tidp,const pthread_attr_t* restrict_attr,void* (

start_rtn)(void

),void *restrict arg);

tidp:事先创建好的pthread_t类型的参数。成功时tidp指向的内存单元被设置为新创建线程的线程ID。

attr:用于定制各种不同的线程属性。通常直接设为NULL。

start_rtn:新创建线程从此函数开始运行。无参数arg设为NULL。我们这里是从消费者、生产者函数开始运行,所以分别设为producer、consumer

arg:start_rtn函数的参数。无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传入。

pthread_join是使一个线程等待另一个线程结束。

代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。

pthread_mutex_destroy(&mutex) 互斥锁销毁函数,现在线程的工作结束,需要将互斥锁销毁,节约资源。

pthread_cond_destroy是用来销毁一个条件变量。



3 哲学家进餐问题

问题背景:

有五个哲学家,他们的生活方式是交替地进行思考和进餐。他们共用一张圆桌,分别坐在五张椅子上。在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐完毕,放下筷子又继续思考。不能让哲学家饿死,即不能发生线程的死锁。


死锁产生的条件:

1、互斥条件:一个资源每次只能被一个进程使用;多个线程所使用的资源中至少有一个是不能共享的。在这里面就是叉子,一个哲学家拿起了叉子,他的邻居就不能再使用

2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;要发生死锁,哲学家必须拿着一只叉子并且等待另一只

3、不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;哲学家必须很有礼貌的等待别人自己放下叉子,而不是去抢叉子

4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;一个任务等待其他任务的资源,后者又在等待另一个任务的资源,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家都得不到全部资源运行而被锁住。也就是0号哲学家等待1号的叉子,1号等待2号的,…,最终4号等待0号的,于是大家都陷入死等状态。


解决思路:一个哲学家拿到左右两只筷子之后才允许其他哲学家进行用餐。用互斥斥量对哲学家左右的筷子进行保护。


在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define N 5
sem_t chopsticks[N];
pthread_mutex_t mutex;
int philosophers[N] = {0, 1, 2, 3, 4};
int get_random()  
{  
    int digit;  
    srand((unsigned)(getpid() + time(NULL)));  
    digit = rand() % 10;  
    return digit;  
}
void *philosopher (void* arg) {
    int i = *(int *)arg;
    int left = i;
    int right = (i + 1) % N;
    while (1) { 
        printf("philosopher %d is thinking\n", i);
        sleep(get_random()); 
        printf("philosopher %d is hungry\n", i);
        pthread_mutex_lock(&mutex);
        sem_wait(&chopsticks[left]);
        printf("philosopher %d takes no %d chopsticks,only one chopstick,he can't hava lunch\n", i, left);
        sem_wait(&chopsticks[right]);
        printf("philosopher %d takes no %d chopsticks \n", i, right);
        pthread_mutex_unlock(&mutex);
        
        printf("philosopher %d have two chopsticks now,he is ready to hava lunch\n", i);
        sleep(get_random()); 
        sem_post(&chopsticks[left]);
        printf("philosopher %d lays down no %d chopstick\n", i, left);
        sem_post(&chopsticks[right]);
        printf("philosopher %d lays down no %d chopstick\n", i, right);
    }
}
int main (int argc, char **argv) {
    srand(time(NULL));
    pthread_t philo[N];
    int i;
    for (i=0; i<N; i++) {
        sem_init(&chopsticks[i], 0, 1);
    }
    pthread_mutex_init(&mutex,NULL);
    for (i=0; i<N; i++) {
        pthread_create(&philo[i], NULL, philosopher, &philosophers[i]);
    }
    for (i=0; i<N; i++) {
        pthread_join(philo[i], NULL);
    }
    for (i=0; i<N; i++) {
        sem_destroy(&chopsticks[i]);
    }
    pthread_mutex_destroy(&mutex);
    return 0;
}

在这里插入图片描述



版权声明:本文为L6666688888原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。