linux编程综合案例(生产者消费者问题)

2023-05-16

linux编程综合案例(生产者消费者问题)

前面一系列练习已经把进程控制、线程、进程间通信的大概知识过了一遍,现在进入综合练习,首先练习经典问题:生产者和消费者问题。(不要问我前面一系列到底是个什么东西,csdn上基本上全都是转载,且都有这句话,我查了半天这个篇文章哪里来的,最早我只能追溯到Linux编程日日练 --生产者消费者问题2010年5月18日的文章,结果文章上面也标明了是转载,也不知道从哪转的,更早的我已经找不到了。
  因为强迫症,实在看不惯前面的人随便转载,为了操作系统的实验又必须学(ノ`Д)ノ,自己又小白不怎么懂/(ㄒoㄒ)/~~,就简单排版,再加些自己了解的简单内容,重新整理编排了下。前半部分主要对这个例子简单加了点分析,后半部分加了点修正。简单看看就好。
  好像有点长,可以从左边目录跳转到需要看的内容。

1.问题概述

多个生产/消费者在有界缓冲上操作。它利用N个字节的共享内存作为有界循环缓冲区,利用写一字符模拟放一个产品,利用读一字符模拟消费一个产品。当缓冲区空时消费者阻塞睡眠,而当缓冲区满时生产者阻塞睡眠。一旦缓冲区中有空单元,生产者进程就向空单元中入写字符,并报告写的内容和位置。一旦缓冲区中有未读过的字符,消费者进程就从该单元中读出字符,并报告读取位置。生产者不能向同一单元中连续写两次以上相同的字符,消费者也不能从同一单元中连续读两次以上相同的字符。

2.问题分析

首先看阶层,有两个,分别是生产者和消费者,他们之间的缓冲区是共享内存,首先想到一点:System V共享内存实现这一个缓冲区;又因为缓冲区是临界资源,所以要用一个互斥信号量实现;生产者和消费者要采用PV信号量操作实现进程同步

因为要求多个进程能同步,所以进程访问缓冲的指针也需要共享内存实现

大致框图:

生产者
消费者
生产者位置指针
消费者位置指针
共享内存

3.原语

进程:Producer - 生产者进程,Consumer - 消费者进程

共有的数据结构:

buffer: array [0…k-1] of integer;
  in,out: 0…k-1;

— in记录第一个空缓冲区,out记录第一个不空的缓冲区

prod_key(缓冲区空的个数),cons_key(缓冲区满的个数),mutex(临界区): semaphore;

— prod_key控制缓冲区不满,cons_key控制缓冲区不空,mutex保护临界区;

初始化prod_key=k,cons_key=0,mutex=1
  
  下面的部分为伪代码

producer(生产者进程):

Item_Type item;
{
    while (true)
    {
        produce(&item);  //生产数据
        p(prod_key);  //缓冲区满,阻塞,prod_key=k,p操作,将运行变为阻塞,申请空闲资源,信号量减一,成功退出,失败阻塞
        p(mutex);  //使用临界资源时,阻塞,mutex=1,临界资源互斥
        buffer[in]:=item;  //将item元素写入buffer,in记录写入元素位置
        in:=(in+1) mod k;
        v(mutex);  //v操作将阻塞变为运行,释放被占用的资源,若有被阻塞的资源,选一个运行
        v(cons_key);  //释放消费者进程资源
    }
}

consumer(消费者进程):

Item_Type item;
{
    while (true)
    {
        p(cons_key);  //cons_key=0,0-1=-1,阻塞,直到produce释放才能0+1,退出阻塞
        p(mutex);  //使用临界资源
        item:=buffer[out];  //将item元素读出buffer,out记录写出元素位置
        out:=(out+1) mod k;
        v(mutex);  //释放临界资源
        v(prod_key);  //释放生产者进程
        consume(&item);  //消费数据
    }
}
producer
申请空闲资源,失败阻塞
申请临界资源
向buffer写入元素
释放临界资源
释放消费者进程
consumer
申请空闲资源,失败阻塞
从buffer读出元素
释放生成者进程

​ 在这个实验中,缓冲区大小被设置为8,producer初始可以申请8个资源,comsumer初始设置为可申请0个,也就是,必须要produce先释放,comsumer,才可以申请,然后运行。

4.函数原型

自定义的函数

get_ipc_id

int get_ipc_id(char *proc_file,key_t key);  //从/proc/sysvipc/文件系统中获取IPC的id号

get_ipc_id() 从/proc/sysvipc/文件系统中获取IPC 的id 号,msg-消息队列,sem-信号量,shm-共享内存

  • pfile: 对应/proc/sysvipc/目录中的IPC 文件分别为

  • key: 对应要获取的IPC 的id 号的键值

  • 返回-1

set_sem()

int set_sem(key_t sem_key,int sem_val,int sem_flag);  //set_sem 函数建立一个信号量,初值设为sem_val

set_sem 函数建立一个具有n 个信号灯的信号量

如果建立成功,返回一个信号灯数组的标识符sem_id

输入参数:

  • sem_key 信号灯数组的键值
  • sem_val 信号灯数组中信号灯的个数
  • sem_flag 信号灯数组的存取权限
  • 返回id

实现过程:建立信号灯->设置信号灯初值->返回ID

set_shm()

char *set_shm(key_t shm_key,int shm_num,int shm_flag);  // set_shm 函数建立一个具有shm_num个字节的共享内存区

set_shm 函数建立一个具有n 个字节的共享内存区

如果建立成功,返回一个指向该内存区首地址的指针shm_buf

输入参数:

  • shm_key 共享内存的键值
  • shm_val 共享内存字节的长度
  • shm_flag 共享内存的存取权限

实现过程:创建共享内存->映射到进程的指针并返回

set_msq()

int set_msq(key_t msq_key,int msq_flag);  //set_msq 函数建立一个消息队列

set_msq 函数建立一个消息队列

如果建立成功,返回一个消息队列的标识符msq_id

  • 输入参数:
  • msq_key 消息队列的键值
  • msq_flag 消息队列的存取权限
  • 返回id

实现过程:创建消息队列->返回ID

P/V操作

int P_operation(int sem_id)

请求P操作

int V_operation(int sem_id)

释放V操作

信号灯上的P/V 操作,sem_num:信号灯数组下标,buf:操作信号灯的结构

  • sem_id:信号灯数组标识符

原有的函数

<sys/sem.h>

引自Linux进程间通信(五)

semget()
int semget(key_t key, int num_sems, int sem_flags);

创建一个新的信号量或取得一个已有的信号量。

  • 参数key是整数值(唯一非零),不相关的进程可以通过它访问同一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键,再由系统生成一个相应的信号标识符(semget()函数的返回值),只有semget()函数才直接使用信号量键,所有其他的信号量函数使用由semget()函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。

  • 参数num_sems指定需要的信号量数目,它的值几乎总是1。

  • 参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

  • semget()函数成功返回一个相应信号标识符(非零),失败返回-1。

semop()
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

用来改变信号量作用

  • 参数sem_id是semget()返回的信号量标识符。

  • 参数sembuf结构体定义如下。

struct sembuf{
	short sem_num;  // 信号索引:除非使用一组信号量,否则它为0,0代表第一个信号
	short sem_op;   // 操作类型:操作标志信号量在一次操作中需要改变的数据,通常是两个数,
                    // 一个是-1,即P(等待)操作,一个是+1,即V(发送信号)操作。
	short sem_flg;  // 操作标志:通常为SEM_UNDO,使操作系统跟踪信号,
                    // 并在进程没有释放该信号量而终止时,操作系统释放信号量
};
  • 参数 nsops 指出将要进行操作的信号的个数。

调用成功返回0,失败返回-1。

semctl
int semctl(int sem_id, int sem_num, int command, ...)

该函数用来直接控制信号量信息

  • 参数sem_id同上。

  • 参数sem_num同上中sembuf结构体中的sem_num。

  • command通常是下面两个值中的其中一个。

    SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。

    IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。

  • arg union semun结构体,详细见链接内容

  • 失败时 semctl() 返回 -1 并设置 errno 指明错误,否则该系统调用返回一个依赖于 cmd 的非负值。

<sys/shm.h>

引自Linux进程间通信(六)

shmget()
int shmget(key_t key, size_t size, int shmflg);

该函数用来创建共享内存。

  • 参数key,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1。

    不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget()函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget()函数的返回值),只有shmget()函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。

  • 参数size,size以字节为单位指定需要共享的内存容量。

  • 参数shmflg,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。

  • 成功返回共享存储的id,失败返回-1。

shmat()
void *shmat(int shm_id, const void *shm_addr, int shmflg);
  • 参数shm_id,shm_id是由shmget()函数返回的共享内存标识。

  • 参数shma_ddr,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。

  • 参数shmflg,shm_flg是一组标志位,通常为0。

  • 调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1。

其他

shmdt()分离共享内存 shmctl()与semctl()一样,控制共享内存。本实验未使用,详见上链接。

<sys/msg.h>

引自消息队列函数

msgget()
int msgget(key_t key, int msgflg);

得到消息队列标识符或创建一个消息队列对象并返回消息队列标识符。

  • key:

    0(IPC_PRIVATE):会建立新的消息队列;

    大于0的32位整数:视参数msgflg来确定操作。通常要求此值来源于ftok返回的IPC键值。

  • msgflg:

    IPC_CREAT:当msgflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,返回此消息队列的标识符;IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列则报错。

  • 成功:返回消息队列的标识符,出错:-1,错误原因存于error中。

其他

msgctl()、msgsnd()、msgrcv()详见上链接。

5.IPC操作函数

我们编写一个函数实现对IPC 信息队列、共享内存、信号量的包装,以备接下来更好的编写程序

头文件ipc.h:

声明ipc操作函数和一些变量

/*Filename : ipc.h*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/msg.h>

#define BUFSZ 256

//建立或获取ipc 的一组函数的原型说明
int get_ipc_id(char *proc_file,key_t key);  //从/proc/sysvipc/文件系统中获取IPC的id号
int set_sem(key_t sem_key,int sem_val,int sem_flag);  //set_sem函数建立一个初值为sem_val的信号量
char *set_shm(key_t shm_key,int shm_num,int shm_flag);  //set_shm函数建立一个具有shm_num个字节的共享内存区,shm_key为共享内存段
int set_msq(key_t msq_key,int msq_flag);  //set_msq 函数建立一个消息队列
int down(int sem_id);
int up(int sem_id);

typedef union semuns  //信号灯控制用的共同体
{
	int val;  //信号灯的个数
} Sem_uns;

typedef struct msgbuf  //消息结构体
{
	long mtype;  //消息类型
	char mtext[1];  //消息内容
} Msg_buf;

//生产消费者共享缓冲区即其有关的变量
key_t buff_key;  //IPC key标识
int buff_num;  //缓冲区长度
char *buff_ptr;  //指向缓冲区地址

//生产者放产品位置的共享指针
key_t pput_key;  //IPC key标识
int pput_num;  //指针个数1
int *pput_ptr;  //生产者放产品位置指针

//消费者取产品位置的共享指针
key_t cget_key;  //IPC key标识
int cget_num;  //指针个数1
int *cget_ptr;  //消费者取产品位置指针

//生产者有关的信号量
key_t prod_key;  //IPC key标识
int prod_sem;  //生产者同步信号量

//消费者有关的信号量
key_t cons_key;  //IPC key标识
int cons_sem;  //消费者同步信号量
int sem_val;  //信号灯个数
int sem_flg;  //使系统跟踪信号,常为SEM_UNDO,在进程未释放该信号量终止时,操作系统释放信号量
int shm_flg;  //读取权限,标志位

//互斥信号量
key_t mtx_key;  //IPC key标识
int mtx_sem;

ipc.c:

​ 这里包装了一些ipc的操作函数,包括信息队列、共享内存、信号量的创建/获得,及PV操作
  这里逐一分析它们的实现过程:(在注释中)

/*ipc.c*/
#include "ipc.h"
int get_ipc_id(char *proc_file,key_t key)  //get_ipc_id() 从/proc/sysvipc/文件系统中获取IPC的id号
{
	FILE *pf;
	int i,j;
	char line[BUFSZ],colum[BUFSZ];
	if((pf = fopen(proc_file,"r")) == NULL)
	{
		perror("Proc file not open");
		exit(EXIT_FAILURE);
	}
	fgets(line, BUFSZ,pf);  //从pf中读取长度为BUFSZ的数组放入line中
	while(!feof(pf))  //如果没有读到文件尾
	{
		i = j = 0;
		fgets(line, BUFSZ,pf);
        while(line[i] == ' ')
           	i++;
        while(line[i] !=' ')
            colum[j++] = line[i++];
        colum[j] = '\0';
        if(atoi(colum) != key)  //atoi()将字符串转为整数
       	    continue;
        j=0;
        while(line[i] == ' ')
       	    i++;
        while(line[i] !=' ')
            colum[j++] = line[i++];
        colum[j] = '\0';
        i = atoi(colum);
        fclose(pf);
        return i;
    }
    fclose(pf);
    return -1;
}

//请求P操作
int P_operation(int sem_id)  //信号灯数组标识符
{
    struct sembuf buf;  //信号灯结构
    buf.sem_op = -1;  //信号量在一次操作中需要改变的数据,-1即p操作等待
    buf.sem_num = 0;  //信号灯数组下标,除非使用一组信号量,否则为0
    buf.sem_flg = SEM_UNDO;  //通常都是undo,使系统跟踪信号
    if((semop(sem_id,&buf,1)) <0)  //调用semop,成功就使之等待,不成功显示错误
    {
        perror("down error ");
        exit(EXIT_FAILURE);
    }
    return EXIT_SUCCESS;
}

//释放 V操作
int V_operation(int sem_id)
{
    struct sembuf buf;  //信号灯结构
    buf.sem_op = 1;  //信号量在一次操作中需要改变的数据,+1即v操作发送信号
    buf.sem_num = 0;  //信号灯数组下标,除非使用1组,否则为0
    buf.sem_flg = SEM_UNDO;  //通常使undo,使系统跟踪信号
    if((semop(sem_id,&buf,1)) < 0)  //调用semop,成功释放占用,使之运行
    {
        perror("up error ");
        exit(EXIT_FAILURE);
    } 
    return EXIT_SUCCESS;
}

int set_sem(key_t sem_key,int sem_val,int sem_flg)  //set_sem 函数建立一个具有n个信号灯的信号量
{
    int sem_id;
    Sem_uns sem_arg;
    //测试由sem_key标识的信号灯数组是否已经建立
    if((sem_id = get_ipc_id("/proc/sysvipc/sem",sem_key)) < 0 )  //获取ipc的id
    {
        //如果没有获取成功,semget新建一个信号灯,其标号返回到sem_id,1代表信号量数目
        if((sem_id = semget(sem_key,1,sem_flg)) < 0)
        {
            perror("semaphore create error");
            exit(EXIT_FAILURE);
        }
        sem_arg.val = sem_val;  //设置信号灯的初值
        if(semctl(sem_id,0,SETVAL,sem_arg) <0)
        {
            perror("semaphore set error");
            exit(EXIT_FAILURE);
        }
    }
    return sem_id;  //返回ipc的id
}

char * set_shm(key_t shm_key,int shm_num,int shm_flg)  //set_shm 函数建立一个具有n个字节的共享内存区
{
    int i,shm_id;
    char * shm_buf;
    //测试由shm_key 标识的共享内存区是否已经建立
    if((shm_id = get_ipc_id("/proc/sysvipc/shm",shm_key)) < 0 )
    {
        //shmget 新建一个长度为shm_num 字节的共享内存,其标号返回到shm_id,权限为shm_flg
        if((shm_id = shmget(shm_key,shm_num,shm_flg)) <0)
        {
            perror("shareMemory set error");
            exit(EXIT_FAILURE);
        }
        //shmat 将由shm_id 标识的共享内存附加给指针shm_buf,该函数用来启用对该共享内存的访问
        if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
        {
            perror("get shareMemory error");
            exit(EXIT_FAILURE);
        }
        for(i=0; i<shm_num; i++)  //共享内存区初始化
            shm_buf[i] = 0; //初始为0
    }
    //shm_key 标识的共享内存区已经建立,将由shm_id 标识的共享内存附加给指针shm_buf
    if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
    {
        perror("get shareMemory error");
        exit(EXIT_FAILURE);
    }
    return shm_buf;
}

int set_msq(key_t msq_key,int msq_flg)  //set_msq 函数建立一个消息队列
{
    int msq_id;
    //测试由msq_key 标识的消息队列是否已经建立
    if((msq_id = get_ipc_id("/proc/sysvipc/msg",msq_key)) < 0 )
    {
        //msgget 新建一个消息队列,其标号返回到msq_id
        if((msq_id = msgget(msq_key,msq_flg)) < 0)
        {
            perror("messageQueue set error");
            exit(EXIT_FAILURE);
        }
    }
    return msq_id;
}

6.生产者程序实现

首先建立(已存在时为打开)一系列的信号量和共享内存,接着就按照操作原语去实现了,代码如下:

/*Filename : producer.c*/
#include "ipc.h"
int main(int argc,char *argv[])
{
	int rate;
	//可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度
	if(argv[1] != NULL)
		rate = atoi(argv[1]);
	else
		rate = 3;  //默认sleep(3)
	
    //共享内存使用的变量,其中键值任给,但是注意键值的
	//唯一性,在另外的文件中要用同一共享内存也要采用统一键值
	buff_key = 101;  //缓冲区任给的键值
	buff_num = 8;  //缓冲区任给的长度
	pput_key = 102;  //生产者放产品指针的键值
	pput_num = 1;  //指针数
	shm_flg = IPC_CREAT | 0644;  //共享内存读写权限,用户具有读写权限,组用户和其它用户具有只读权限
	//获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
	buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
	//获取生产者放产品位置指针pput_ptr
	pput_ptr = (int *)set_shm(pput_key,pput_num,shm_flg);
    
	//信号量使用的变量,键值与comesumer.c中相同
	prod_key = 201;  //生产者同步信号灯键值
	mtx_key = 202;  //互斥信号灯键值
	cons_key = 301;  //消费者同步信号灯键值
	sem_flg = IPC_CREAT | 0644;
    
	sem_val = buff_num;  //生产者同步信号灯初值设为缓冲区最大可用量8
	//获取生产者同步信号灯,引用标识存prod_sem
	prod_sem = set_sem(prod_key,sem_val,sem_flg);
	//消费者初始无产品可取,同步信号灯初值设为0
	sem_val = 0;
	//获取消费者同步信号灯,引用标识存cons_sem
	cons_sem = set_sem(cons_key,sem_val,sem_flg);
	//生产者互斥信号灯初值为1
	sem_val = 1;
	//获取互斥信号灯,引用标识存mtx_sem
	mtx_sem = set_sem(mtx_key,sem_val,sem_flg);

	while(1)  //循环执行模拟生产者不断放产品
	{
		//如果缓冲区满则生产者阻塞
		P_operation(prod_sem);
		//如果有进程进入,本生产者阻塞
		P_operation(mtx_sem);
		//用写一字符的形式模拟生产者放产品,报告本进程号和放入的字符及存放的位置
		buff_ptr[*pput_ptr] = 'A'+ *pput_ptr;
		//挂起rate秒
		sleep(rate);
		printf("%d producer put: %c to Buffer[%d]\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
		//存放位置循环下移
		*pput_ptr = (*pput_ptr+1) % buff_num;
		//唤醒阻塞的进程
		V_operation(mtx_sem);
		//唤醒阻塞的消费者
		V_operation(cons_sem);
	}
	return EXIT_SUCCESS;
}

7.消费者程序实现

如同生产者上述,代码如下:

/* consumer.c*/
#include "ipc.h"
int main(int argc,char *argv[])
{
	int rate;
	//可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度
	if(argv[1] != NULL)
		rate = atoi(argv[1]);
	else
		rate = 3; //默认为3 秒
	
    //共享内存使用的变量
	buff_key = 101; //缓冲区任给的键值
	buff_num = 8; //缓冲区任给的长度
	cget_key = 103; //消费者取产品指针的键值
	cget_num = 1; //指针数
	shm_flg = IPC_CREAT | 0644; //共享内存读写权限
	//获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
	buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
	//获取消费者取产品指针,cget_ptr 指向索引地址
	cget_ptr = (int *)set_shm(cget_key,cget_num,shm_flg);
	
    //信号量使用的变量,键值与producer中相同
	prod_key = 201; //生产者同步信号灯键值
	mtx_key = 202; //互斥信号灯键值
	cons_key = 301; //消费者同步信号灯键值
	sem_flg = IPC_CREAT | 0644; //信号灯操作权限
    
	//生产者同步信号灯初值设为缓冲区最大可用量8
	sem_val = buff_num;
	//获取生产者同步信号灯,引用标识存prod_sem
	prod_sem = set_sem(prod_key,sem_val,sem_flg);
	//消费者初始无产品可取,同步信号灯初值设为0
	sem_val = 0;
	//获取消费者同步信号灯,引用标识存cons_sem
	cons_sem = set_sem(cons_key,sem_val,sem_flg);
	//消费者互斥信号灯初值为1
	sem_val = 1;
	//获取互斥信号灯,引用标识存mtx_sem
	mtx_sem = set_sem(mtx_key,sem_val,sem_flg);

	while(1)  //循环执行模拟消费者不断取产品
    {
		//如果无产品消费者阻塞
		P_operation(cons_sem);
		//如果有进程进入,本消费者阻塞
		P_operation(mtx_sem);
		//用读一字符的形式模拟消费者取产品,报告本进程号和获取的字符及读取的位置
		sleep(rate);
		printf("%d consumer get: %c fromBuffer[%d]\n",getpid(),buff_ptr[*cget_ptr],*cget_ptr);
		//读取位置循环下移
		*cget_ptr = (*cget_ptr+1) % buff_num;
		//唤醒阻塞的进程
		V_operation(mtx_sem);
		//唤醒阻塞的生产者
		V_operation(prod_sem);
	}
	return EXIT_SUCCESS;
}

8.编写Makefile

由于本项目工程有多个文件,所以需要Makefile来方便编译
  $emacs makefile

hdrs = ipc.h
opts = -g -c
c_src = consumer.c ipc.c
c_obj = consumer.o ipc.o
p_src = producer.c ipc.c
p_obj = producer.o ipc.o
all: producer consumer
consumer: $(c_obj)
	gcc $(c_obj) -o consumer
consumer.o: $(c_src) $(hdrs)
	gcc $(opts) $(c_src)
producer: $(p_obj)
	gcc $(p_obj) -o producer
producer.o: $(p_src) $(hdrs)
	gcc $(opts) $(p_src)
clean:
	rm consumer producer *.o

在这里插入图片描述

9.编译

$ make

运行时打开多个终端窗口,输入

$./producer 1

另一个窗口输入:

$./consumer 1

这时可以看到同步过程
  在这里插入图片描述

​ 这里的多个,不是指两个,可以多输入几个producer,consumer,时间也可以不一样,这样才是多个生产/消费者。

总结

这个实验,建立了ipc.h头文件,ipc.c,consumer.c,producer.c四个文件,然后用makefile进行编译。在编写过程中,用了共享内存,消息队列,还有信号,所以,就很复杂。
它的consumer和producer是分开进行的,也可以写多个fork(),将这几个程序合并为一个,一起运行。
小白大概就似懂非懂写了这么些(´。_。`)

补充

合并后的源代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/msg.h>

#define BUFSZ 256

typedef union semuns  //信号灯控制用的共同体
{
	int val;  //信号灯的个数
} Sem_uns;

typedef struct msgbuf  //消息结构体
{
	long mtype;  //消息类型
	char mtext[1];  //消息内容
} Msg_buf;

//生产消费者共享缓冲区即其有关的变量
key_t buff_key;  //IPC key标识
int buff_num;  //缓冲区长度
char *buff_ptr;  //指向缓冲区地址

//生产者放产品位置的共享指针
key_t pput_key;  //IPC key标识
int pput_num;  //指针个数1
int *pput_ptr;  //生产者放产品位置指针

//消费者取产品位置的共享指针
key_t cget_key;  //IPC key标识
int cget_num;  //指针个数1
int *cget_ptr;  //消费者取产品位置指针

//生产者有关的信号量
key_t prod_key;  //IPC key标识
int prod_sem;  //生产者同步信号量

//消费者有关的信号量
key_t cons_key;  //IPC key标识
int cons_sem;  //消费者同步信号量
int sem_val;  //信号灯个数
int sem_flg;  //使系统跟踪信号,常为SEM_UNDO,在进程未释放该信号量终止时,操作系统释放信号量
int shm_flg;  //读取权限,标志位

//互斥信号量
key_t mtx_key;  //IPC key标识
int mtx_sem;

int get_ipc_id(char *proc_file,key_t key)  //get_ipc_id() 从/proc/sysvipc/文件系统中获取IPC的id号
{
	FILE *pf;
	int i,j;
	char line[BUFSZ],colum[BUFSZ];
	if((pf = fopen(proc_file,"r")) == NULL)
	{
		perror("Proc file not open");
		exit(EXIT_FAILURE);
	}
	fgets(line, BUFSZ,pf);  //从pf中读取长度为BUFSZ的数组放入line中
	while(!feof(pf))  //如果没有读到文件尾
	{
		i = j = 0;
		fgets(line, BUFSZ,pf);
        while(line[i] == ' ')
           	i++;
        while(line[i] !=' ')
            colum[j++] = line[i++];
        colum[j] = '\0';
        if(atoi(colum) != key)  //atoi()将字符串转为整数
       	    continue;
        j=0;
        while(line[i] == ' ')
       	    i++;
        while(line[i] !=' ')
            colum[j++] = line[i++];
        colum[j] = '\0';
        i = atoi(colum);
        fclose(pf);
        return i;
    }
    fclose(pf);
    return -1;
}

//请求P操作
int P_operation(int sem_id)  //信号灯数组标识符
{
    struct sembuf buf;  //信号灯结构
    buf.sem_op = -1;  //信号量在一次操作中需要改变的数据,-1即p操作等待
    buf.sem_num = 0;  //信号灯数组下标,除非使用一组信号量,否则为0
    buf.sem_flg = SEM_UNDO;  //通常都是undo,使系统跟踪信号
    if((semop(sem_id,&buf,1)) <0)  //调用semop,成功就使之等待,不成功显示错误
    {
        perror("down error ");
        exit(EXIT_FAILURE);
    }
    return EXIT_SUCCESS;
}

//释放 V操作
int V_operation(int sem_id)
{
    struct sembuf buf;  //信号灯结构
    buf.sem_op = 1;  //信号量在一次操作中需要改变的数据,+1即v操作发送信号
    buf.sem_num = 0;  //信号灯数组下标,除非使用1组,否则为0
    buf.sem_flg = SEM_UNDO;  //通常使undo,使系统跟踪信号
    if((semop(sem_id,&buf,1)) < 0)  //调用semop,成功释放占用,使之运行
    {
        perror("up error ");
        exit(EXIT_FAILURE);
    } 
    return EXIT_SUCCESS;
}

int set_sem(key_t sem_key,int sem_val,int sem_flg)  //set_sem 函数建立一个具有n个信号灯的信号量
{
    int sem_id;
    Sem_uns sem_arg;
    //测试由sem_key标识的信号灯数组是否已经建立
    if((sem_id = get_ipc_id("/proc/sysvipc/sem",sem_key)) < 0 )  //获取ipc的id
    {
        //如果没有获取成功,semget新建一个信号灯,其标号返回到sem_id,1代表信号量数目
        if((sem_id = semget(sem_key,1,sem_flg)) < 0)
        {
            perror("semaphore create error");
            exit(EXIT_FAILURE);
        }
        sem_arg.val = sem_val;  //设置信号灯的初值
        if(semctl(sem_id,0,SETVAL,sem_arg) <0)
        {
            perror("semaphore set error");
            exit(EXIT_FAILURE);
        }
    }
    return sem_id;  //返回ipc的id
}

char * set_shm(key_t shm_key,int shm_num,int shm_flg)  //set_shm 函数建立一个具有n个字节的共享内存区
{
    int i,shm_id;
    char * shm_buf;
    //测试由shm_key 标识的共享内存区是否已经建立
    if((shm_id = get_ipc_id("/proc/sysvipc/shm",shm_key)) < 0 )
    {
        //shmget 新建一个长度为shm_num 字节的共享内存,其标号返回到shm_id,权限为shm_flg
        if((shm_id = shmget(shm_key,shm_num,shm_flg)) <0)
        {
            perror("shareMemory set error");
            exit(EXIT_FAILURE);
        }
        //shmat 将由shm_id 标识的共享内存附加给指针shm_buf,该函数用来启用对该共享内存的访问
        if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
        {
            perror("get shareMemory error");
            exit(EXIT_FAILURE);
        }
        for(i=0; i<shm_num; i++)  //共享内存区初始化
            shm_buf[i] = 0; //初始为0
    }
    //shm_key 标识的共享内存区已经建立,将由shm_id 标识的共享内存附加给指针shm_buf
    if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
    {
        perror("get shareMemory error");
        exit(EXIT_FAILURE);
    }
    return shm_buf;
}

int set_msq(key_t msq_key,int msq_flg)  //set_msq 函数建立一个消息队列
{
    int msq_id;
    //测试由msq_key 标识的消息队列是否已经建立
    if((msq_id = get_ipc_id("/proc/sysvipc/msg",msq_key)) < 0 )
    {
        //msgget 新建一个消息队列,其标号返回到msq_id
        if((msq_id = msgget(msq_key,msq_flg)) < 0)
        {
            perror("messageQueue set error");
            exit(EXIT_FAILURE);
        }
    }
    return msq_id;
}

int main()
{
  int rate = 3;
	pid_t fpid = fork();
       	fpid = fork();
    //共享内存使用的变量,其中键值任给,但是注意键值的
	//唯一性,在另外的文件中要用同一共享内存也要采用统一键值
	buff_key = 101;  //缓冲区任给的键值 key_t
	buff_num = 8;  //缓冲区任给的长度 int
	pput_key = 102;  //生产者放产品指针的键值 key_t
	pput_num = 1;  //指针数 int
    cget_key = 103; //消费者取产品指针的键值 key_t
	cget_num = 1; //指针数 int
	shm_flg = IPC_CREAT | 0644;  //int 共享内存读写权限,用户具有读写权限,组用户和其它用户具有只读权限
	//获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址 char*
	buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
	//获取生产者放产品位置指针pput_ptr char*
	pput_ptr = (int *)set_shm(pput_key,pput_num,shm_flg);
	//获取消费者取产品指针,cget_ptr 指向索引地址 char*
	cget_ptr = (int *)set_shm(cget_key,cget_num,shm_flg);
    
	//信号量使用的变量,键值与comesumer.c中相同
	prod_key = 201;  //生产者同步信号灯键值 ket_t
	mtx_key = 202;  //互斥信号灯键值 ket_t
	cons_key = 301;  //消费者同步信号灯键值 key_t
	sem_flg = IPC_CREAT | 0644;  //int
	
	sem_val = buff_num;  //生产者同步信号灯初值设为缓冲区最大可用量8 int
	//获取生产者同步信号灯,引用标识存prod_sem int
	prod_sem = set_sem(prod_key,sem_val,sem_flg);
	//消费者初始无产品可取,同步信号灯初值设为0
	sem_val = 0;
	//获取消费者同步信号灯,引用标识存cons_sem int
	cons_sem = set_sem(cons_key,sem_val,sem_flg);
	//生产者互斥信号灯初值为1
	sem_val = 1;
	//获取互斥信号灯,引用标识存mtx_sem i
	mtx_sem = set_sem(mtx_key,sem_val,sem_flg);
	
	if(fpid != 0)
	{
	    while(1)  //循环执行模拟生产者不断放产品
	    {
	        //如果缓冲区满则生产者阻塞
	        P_operation(prod_sem);
	        //如果有进程进入,本生产者阻塞
	        P_operation(mtx_sem);
	        //用写一字符的形式模拟生产者放产品,报告本进程号和放入的字符及存放的位置
	        buff_ptr[*pput_ptr] = 'A'+ *pput_ptr;
	        //挂起rate秒
	        sleep(rate-1);
	        printf("%d producer put: %c to Buffer[%d]\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
	        //存放位置循环下移
	        *pput_ptr = (*pput_ptr+1) % buff_num;
	        //唤醒阻塞的进程
	        V_operation(mtx_sem);
	        //唤醒阻塞的消费者
	        V_operation(cons_sem);
	    }
	}else
	{
	    while(1)  //循环执行模拟消费者不断取产品
	    {
	        //如果无产品消费者阻塞
	        P_operation(cons_sem);
	        //如果有进程进入,本消费者阻塞
	        P_operation(mtx_sem);
	        //用读一字符的形式模拟消费者取产品,报告本进程号和获取的字符及读取的位置
	        //sleep(rate);
	        printf("%d consumer get: %c fromBuffer[%d]\n",getpid(),buff_ptr[*cget_ptr],*cget_ptr);
	        //读取位置循环下移
	        *cget_ptr = (*cget_ptr+1) % buff_num;
	        //唤醒阻塞的进程
	        V_operation(mtx_sem);
	        //唤醒阻塞的生产者
	        V_operation(prod_sem);
	    }
	}
	return EXIT_SUCCESS;
}
循环
循环
缓冲区初始化
生产者
消费者

根据代码重置流程图

设置生产互斥消费者信号量
设置键值
建立共享内存区
消费者初始化
生产者初始化
缓冲区初始化
prod/mtx/cons
生产201互斥202消费301
buff/pput/cget_ptr
键值102 个数1
键值102 个数1
键值101 长度8
初始化完成
生产者开始
P生产者互斥信号
运行操作
V互斥消费者信号
结束
消费者开始
P消费者互斥信号
运行操作
V互斥生产者信号
结束

关于源代码中的一点问题

仔细分析运行的流程图后发现似乎程序有些内容并不重要

  1. 头文件ipc.h中,”int down(int sem_id);” 与 “int up(int sem_id);”这两个函数在这个实验中并没有用到。而ipc.c中的”int P_operation(int sem_id)“ 与 ”int V_operation(int sem_id)“并没有被声明,所以此处的两个函数声明因换为PV操作的函数声明;
  2. <sys/msg.h>,事实上,这份代码只在ipc.h与ipc.c中写了关于msg的内容,实验过程中,完全没有用上这部分内容,可以删去所有相关内容;
  3. int get_ipc_id(char * proc_file,key_t key) ;这个函数是为了检查原本是否有sem_id,检查信号量是否建立,如果没有,就调用 semget()shmget(),但事实上这两个函数本身就会根据键值去查找是否已经建立,如果建立就返回已建立的信息,没有就建立,所以这个函数也可以删去<sys/ipc.h>也可以直接删掉;
  4. 这个源程序反复运行,后,单独运行producer或consumer可能有bug,嗯,小白搞不明白。
  5. (这之后都是新加的)补充上一点,后来发现sem_id在系统文件中未删除,也就是上一次的运行数据仍然保留,第一次运行后再次运行都是基于之前的结果运行,不知道为什么会出现bug
  6. 为了解决这个问题,就在最终代码删除了这三个的sem_id,之后每一次运行这个程序都会重新创建来避免出现bug
  7. 这个代码原本,往共享区放东西,然后生产者取,取了之后只是读取而已,也就是说,只要共享区被放过东西,那么生产者就可以无限的从中读出数据。大概就是这样……,也正式,第四点,运行过一次后,可以单独运行consumer.c,还能取出东西的原因。

最终的精简代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/types.h>

int set_sem(key_t sem_key,int sem_val,int sem_flg)  //set_sem函数建立一个具初值为semval信号量
{
    int sem_id;
    int sem_arg;
    if((sem_id = semget(sem_key,1,sem_flg)) < 0)
    {  //创建一个新的信号量或取得一个原有的信号量
        perror("semaphore create error");
        exit(1);
    }
    if(semctl(sem_id,0,SETVAL,sem_val) <0)
    {  //初始化信号量
        perror("semaphore set error");
        exit(1);
    }
    return sem_id;  //返回ipc的id
}

char * set_shm(key_t shm_key,int shm_num,int shm_flg)  //set_shm函数建立一个具有n个字节的共享内存区
{
    int i,shm_id;
    char * shm_buf;
    //shmget 新建一个长度为shm_num字节的共享内存,其标号返回到shm_id,权限为shm_flg
    if((shm_id = shmget(shm_key,shm_num,shm_flg)) <0)
    {
        perror("shareMemory set error");
        exit(1);
    }
    //shmat 将由shm_id标识的共享内存附加给指针shm_buf,该函数用来启用对该共享内存的访问
    if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
    {
        perror("get shareMemory error");
        exit(1);
    }
    for(i=0; i<shm_num; i++)  //共享内存区初始化
        shm_buf[i] = 0; //初始为0
    return shm_buf;
}

//请求P操作
int P_operation(int sem_id)  //信号灯数组标识符
{
    struct sembuf buf={0,-1,SEM_UNDO};  //信号灯结构,0信号量编号,-1 p操作
    if((semop(sem_id,&buf,1)) <0)  //调用semop,成功就使之等待,不成功显示错误
    {
        perror("请求失败");
        exit(1);
    }
    return 0;
}

//释放 V操作
int V_operation(int sem_id)
{
    struct sembuf buf={0,1,SEM_UNDO};  //信号灯结构
    if((semop(sem_id,&buf,1)) < 0)  //调用semop,成功释放占用,使之运行
    {
        perror("释放失败");
        exit(1);
    }
    return 0;
}

int main()
{
    int rate = 3;
    pid_t fpid = fork();
    fpid = fork();  //两个fork,模拟多生产者消费者
    //共享内存使用的变量,其中键值任给
    key_t buff_key = 104;  //缓冲区任给的键值
    int buff_num = 8;  //缓冲区任给的长度
    key_t pput_key = 105;  //生产者放产品指针的键值
    int pput_num = 1;  //指针数
    key_t cget_key = 106; //消费者取产品指针的键值
    int cget_num = 1; //指针数
    int shm_flg = IPC_CREAT | 0644;  //共享内存读写权限,用户具有读写权限,组用户和其它用户具有只读权限
    char* buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);  //获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址 char*
    int* pput_ptr = (int *)set_shm(pput_key,pput_num,shm_flg);  //获取生产者放产品位置指针
    int* cget_ptr = (int *)set_shm(cget_key,cget_num,shm_flg);  //获取消费者取产品指针

    key_t prod_key = 205;  //生产者同步信号灯键值
    key_t mtx_key = 206;  //互斥信号灯键值
    key_t cons_key = 207;  //消费者同步信号灯键值
    int sem_flg = IPC_CREAT | 0644;  //权限

    int prod_sem = set_sem(prod_key,buff_num,sem_flg);  //得到信号量id
    int cons_sem = set_sem(cons_key,0,sem_flg);  //得到信号量id
    int mtx_sem = set_sem(mtx_key,1,sem_flg);  //得到信号量id
    if (fpid != 0)  //父进程生产
    {
        for (int i = 0; i < 20; i++)  //循环执行模拟生产者不断放产品
        {
            int s = rand()%2+1;  //随机睡眠一段时间
            P_operation(prod_sem); //如果缓冲区满则生产者阻塞
            P_operation(mtx_sem);  //如果有进程进入,本生产者阻塞
            sleep(s);     //挂起s秒
            buff_ptr[*pput_ptr] = 65 + (*pput_ptr);  //用写一字符的形式模拟生产者放产品,报告本进程号和放入的字符及存放的位置,原本代码这个操作执行完后就是在白做,放了又放一遍
            printf("%d producer put: %c to Buffer[%d]|\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
            *pput_ptr = (*pput_ptr+1) % buff_num;  //存放位置循环下移
            V_operation(mtx_sem);  //唤醒阻塞的进程
            V_operation(cons_sem);  //唤醒阻塞的消费者
        }
    }
    else  //子进程消费
    {
        for (int i = 0; i < 20; i++) //循环执行模拟消费者不断取产品
        {
            int s = rand()%2+1;  //随机睡眠一段时间
            P_operation(cons_sem);  //如果无产品消费者阻塞
            P_operation(mtx_sem);  //如果有进程进入,本消费者阻塞s
            sleep(s); //挂起s秒,用读一字符的形式模拟消费者取产品,报告本进程号和获取的字符及读取的位置
            printf("                                 |");
            printf("%d consumer get: %c fromBuffer[%d]\n", getpid(),buff_ptr[*cget_ptr], *cget_ptr);
            buff_ptr[*cget_ptr] = 0;  //该物品被取走,原本代码没有取走操作!!
            *cget_ptr = (*cget_ptr+1) % buff_num;  //读取位置循环下移
            V_operation(mtx_sem);  //唤醒阻塞的进程
            V_operation(prod_sem);  //唤醒阻塞的生产者
        }
    }
    semctl(cons_sem, 0, IPC_RMID);
    semctl(prod_sem, 0, IPC_RMID);
    semctl(mtx_sem, 0, IPC_RMID);
    return 0;
}

运行结果是这样的
左边生产者,右边消费者
这个代码的fork了两次,两个生产者,两个消费者,谁先到最后,会删除sem_id,然后后面的进程会因为没有id了就停止了。
然后,我亲爱的好证明老师问sleep如果去掉,运行结果是否会相同,我实测是有部分差别的,但是得到的回答是应该相同的。呃,因为互斥的话,似乎,确实该相同,但是,为什么最后运行结果又不同。欸,对,sleep的初衷是为了我能在运行的时候看清咋运行的,随机的睡眠时间则是因为我起初也认为睡眠时间不同会使结果不同。

	头大,不懂,岂可修!/(ㄒoㄒ)/~~
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux编程综合案例(生产者消费者问题) 的相关文章

  • java自带的注解@ PostConstruct

    java注解 64 PostConstructor 1 spring项目加载数据字典 64 PostConstruct注解的方法在项目启动的时候执行这个方法 xff0c 也可以理解为在spring容器启动的时候执行 xff0c 可作为一些数
  • 排序算法总结

    原文链接 https mp weixin qq com s HQg3BzzQfJXcWyltsgOfCQ 本文将采取动态图 43 文字描述 43 正确的java代码实现来讲解以下十大排序算法 xff1a 冒泡排序 选择排序 插入排序 希尔排
  • 关于rebase

    场景复现 xff1a 本来要在refund分支上的修改的代码 xff0c 结果由于分支太多写在了queue分支上 如何恢复queue分支到提交之前的版本 xff1f xff1f 1 git log 找到commitid 2 git rese
  • macOS big sur Navicat Premium12.1.15 无法正常启动

    提示信息 xff1a Navicat Premium 因为出现问题而无法打开 错误日志提示 Dyld Error Message dyld Using shared cache 1E362DBC F66C 3135 BCA0 C1BBAE1
  • H5活动页面遇到的坑+微信分享代码

    h5活动页面功能 xff1a 在手机上微信分享 1 上传两张图片 2 播放一个背景音乐 很简单是么 xff1f 那说明你知道的太少了 xff0c 其实里面的坑好多 一下是制作的心路历程 xff1a 坑1 iphone上传照片的时候 xff0
  • mybatis parametertype 多个参数

    一 单个参数 xff1a public List lt XXBean gt getXXBeanList 64 param 34 id 34 String id lt select id 61 34 getXXXBeanList 34 par
  • JS获取地址中的参数

    lt DOCTYPE HTML PUBLIC 34 W3C DTD HTML 4 0 Transitional EN 34 gt lt html gt lt head gt lt title gt 打印 lt title gt lt met
  • less 查看日志,并且搜索

    一 关键字搜索日志 非常实用 1 less catalina out 2 大写字母 xff1a F find的意思 xff0c 并且其实他正在计算行数 直接到达日志最底部 xff0c 也就是最新日志 3 xff1a ctrl 43 c 把上
  • maven配置,以及项目"Dependency 'xxxx‘ not found"解决过程

    maven安装 1 下载maven文件 地址 2 解压好就可以了 xff0c 无需安装 xff0c 3 修改下面配置文件 配置環境变量 xff1a xff08 和配置jdk一样 xff09 检查配置成功没有 xff1a 直接cmd mvn
  • linux修改系统时间

    一 查看和修改Linux的时区1 查看当前时区 命令 xff1a 34 date R 34 1 修改设置Linux服务器时区方法 A 命令 xff1a 34 tzselect 34 依据引导进行选择 二 查看和修改Linux的时间1 查看时
  • Win2012系统忘记密码,修改密码。

    请准备一张相应操作系统版本的光盘 Server2012R2安装光盘ISO 步骤 1在虚拟机的光盘中选择Server2012R2的ISO 并确定 如果是物理机 直接把ISO刻录成光盘 放入光驱即可 2重启服务器 修改启动项从CD ROM启动
  • 防火墙firewall-cmd

    防火墙firewall cmd 一 centos7查看防火墙所有信息 firewall cmd list all 二 centos7查看防火墙开放的端口信息 firewall cmd list ports 三 开放 删除端口号 3 1 开放
  • docker与firewalld冲突解决

    firewall的底层是使用iptables进行数据过滤 xff0c 建立在iptables之上 xff0c 而docker使用iptables来进行网络隔离和管理 xff0c 这可能会与 Docker 产生冲突 当 firewalld 启
  • gradle和gradle wrapper

    wrapper保证了团队中每一个开发者都使用同样版本的Gradle并能使用Gradle进行项目构建 1 Gradle Wrapper 是什么 Gradle Wrapper 由几个文件组成 xff0c 这些文件在你的项目目录中 l gradl
  • ApplicationContextAware及InitializingBean及bean注入执行顺序

    1 spring先检查注解注入的 bean xff0c 并将它们实例化 2 然后 spring初始化 bean 的顺序是按照 xml 中配置的顺序依次执行构造 3 如果某个类实现了 ApplicationContextAware接口 xff
  • git 报错: http request failed

    解决方案 xff1a 卸载Centos自带的git1 7 1 xff0c 安装版本git2 2 1 1 查看当前git版本 git version git version 1 7 1 2 卸载git1 7 1 yum remove git
  • 关于sh时命令不识别无法正常执行.sh文件

    为什么不能执行呢 xff0c 因为我们的命令很多时候是以bash的规范的 xff0c 所以如果shell不是bash类型 xff0c 很容易出现命令不识别这类问题 先检查shell类型 echo SHELL 这是正常的 xff0c 是bas
  • Powershell美化(oh-my-posh)

    效果展示 xff1a 1 通过cmd下载oh my posh或者直接微软商店搜索下载 winget install oh my posh 2 打开powershell 7或者powershell xff08 推荐以下载的最高版本为主 xff
  • O2OA中如何使用PostgreSQL + Citus 实现分布式数据库实现方案?

    虽然 O2OA 数据表高效的表结构以及索引的设计已经极大程度地保障了数据存取操作的性能 xff0c 但是随着使用时间从增长 xff0c 数据表存放的数据量也会急剧增长 此时 xff0c 仍然需要有合适的方案来解决数据量产生的系统性能瓶颈 本
  • POI Excel导出样式设置

    HSSFSheet sheet 61 workbook createSheet 34 sheetName 34 创建sheet sheet setVerticallyCenter true 下面样式可作为导出左右分栏的表格模板 sheet

随机推荐