进程间协作、同步

进程和进程之间无非是通过磁盘、内核、用户空间传输数据。

通过磁盘(也就是文件)实现进程通信这个好理解,服务器进程把计算结果写入文件,客户端进程从文件读数据就可以了。这里的竞争条件是当服务端正在写文件时,客户端是不允许读的。

命名管道把数据写入文件,因此它可以独立于进程存在。但是命名管道是一个队列而非常规的文件,当读者把数据读走后,数据就不存在了,下一次读到的是后面的内容。

普通管道位于内核,用于父子进程间通信,因此它的存在依赖于进程的存在。

进程间通过文件或FIFO传输数据时,write将数据从内存复制到内核缓冲区,read将数据从内核缓冲区复制到内存。而使用共享内存是不存在用户空间和内核空间的来回复制的。共享内存段是用户内存的一部分,不同的进程都拥有指向此内存段的指针。进程依靠访问权限来对这段内存进行读或写。

上面已提到通过文件实现进程间通信时需要对进程进行同步,用到文件锁

下面的代码服务端负责向文件写入当前时间,客户端读取该文件。

服务端:

#include<sys/file.h>
#include<stdio.h>
#include<fcntl.h>
#include<time.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>

#define oops(m,x) {\
    perror(m);    \
    exit(x);    \
}

main(int argc,char *argv[]){
    int fd;
    time_t now;
    char* message;
    if(argc!=2){
        fprintf(stderr,"usage:%s filename\n",argv[0]);
        exit(1);
    }
    if((fd=open(argv[1],O_CREAT|O_TRUNC|O_WRONLY,0644))==0)
        oops(argv[1],2);
    void lock_opreation(int,int);
    while(1){
        time(&now);
        message=ctime(&now);
        if(lseek(fd,0,SEEK_SET)==-1)
            oops("lseek",3);
        lock_operation(fd,F_WRLCK);
        if(write(fd,message,strlen(message))==-1)
            oops("write",4);
        lock_operation(fd,F_UNLCK);
        sleep(1);
    }
}

lock_operation(int fd,int op){
    struct flock lock;
    lock.l_whence=SEEK_SET;
    lock.l_start=0;
    lock.l_len=0;
    lock.l_pid=getpid();
    lock.l_type=op;
    if(fcntl(fd,F_SETLKW,&lock)==-1)
        oops("locl operation",6);
}

客户端:

#include<sys/file.h>
#include<stdio.h>
#include<fcntl.h>
#include<time.h>
#include<stdlib.h>
#include<string.h>
#define oops(m,x) {perror(m);exit(x);}
#define BUFSIZE 50

main(int argc,char *argv[]){
    int fd,nread;
    char buf[BUFSIZE];
    if(argc!=2){
        fprintf(stderr,"usage:%s filename\n",argv[0]);
        exit(1);
    }
    if((fd=open(argv[1],O_RDONLY))==-1)
        oops(argv[1],2);
    while(1){
        lock_operation(fd,F_RDLCK);     //读锁
        nread=read(fd,buf,BUFSIZE);    
        lock_operation(fd,F_UNLCK); //解除读锁
        if(nread>0){
            printf("pid %d: %s",getpid(),buf);
            bzero(buf,BUFSIZE);
        }
        lseek(fd,0,SEEK_SET);
        sleep(3);
    }
    close(fd);
}
lock_operation(int fd,int op){
    struct flock lock;
    lock.l_whence=SEEK_SET;
    lock.l_start=0;     //锁住文件的起始位置
    lock.l_len=0;       //锁住的长度。0表示到文件结尾
    lock.l_pid=getpid();    //哪个进程可能操作该文件
    lock.l_type=op;     //锁的类型。读、写or解锁
    if(fcntl(fd,F_SETLKW,&lock)==-1)    //通过fcntl给文件设置锁
        oops("lock operation",6);
}

使用共享内存的多个进程之间通过信号量来协同工作。信号量是一个内核变量,它是系统级的全局变量,可以被系统中的任何进程所访问。

下面的代码是服务端向共享内存写入当前时间:

#include<stdio.h>
#include<sys/shm.h>
#include<time.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
#include<sys/sem.h>
#include<signal.h>

#define TIME_MEM_KEY 99
#define TIME_SEM_KEY 9900
#define SEG_SIZE ((size_t)100)
#define oops(m,x) {perror(m);exit(1);}

union semnum{
    int val;
    struct semid_ds *buf;        //信号量控制结构体
    ushort *array;
};
int seg_id,semset_id;
void clearup(int);
main(){
    char *mem_ptr,*ctime();        //变量和函数放在一起声明
    time_t now;
    int n;
    seg_id=shmget(TIME_MEM_KEY,SEG_SIZE,IPC_CREAT|0777);    //共享内存有自己单独的读写权限0777
    if(seg_id==-1)
        oops("shmget",1);
    mem_ptr=shmat(seg_id,NULL,0);
    if(mem_ptr==(void*)-1)
        oops("shmat",2);
    semset_id=semget(TIME_SEM_KEY,2,0666|IPC_CREAT|IPC_EXCL);   //产生一个信号量集合,容量为2
    if(semset_id==-1)
        oops("semget",3);
    set_sem_value(semset_id,0,0);       //设置第一个信号量的值
    set_sem_value(semset_id,1,0);       //设置第二个信号量的值
    signal(SIGINT,clearup);     //SIGINT信号到来时做一些清理工作
    for(n=0;n<60;n++){
        time(&now);
        printf("\tshm_ts2 waiting for lock\n");
        wait_and_lock(semset_id);       //锁住共享内存
        printf("\tshm_ts2 updating memeory\n");
        strcpy(mem_ptr,ctime(&now));    //向共享内存写入数据
        release_lock(semset_id);        //释放信号量
        printf("\tshm_ts2 released lock\n");
        sleep(1);
    }
    clearup(0);
}
void clearup(int n){
    shmctl(seg_id,IPC_RMID,NULL);       //删除共享内存
    semctl(semset_id,0,IPC_RMID,NULL);  //删除信号量
}
set_sem_value(int semset_id,int semnum,int val){
    union semnum initval;
    initval.val=val;
    if(semctl(semset_id,semnum,SETVAL,initval)==-1)
        oops("semctl",4);
}
wait_and_lock(int semset_id){
    struct sembuf actions[2];        //对信号量进行制作的结构体
    actions[0].sem_num=0;       //操作第1个信号量
    actions[0].sem_flg=SEM_UNDO;    //进程结束时,进程对信号量的操作将被撤销(信号量属于IPC对象,IPC对象是独立于进程的)
    actions[0].sem_op=0;        //阻塞直到信号量变为0
    actions[1].sem_num=1;
    actions[1].sem_flg=SEM_UNDO;
    actions[1].sem_op=+1;       //信号量的值加1,表示要释放该信号控制的资源
    if(semop(semset_id,actions,2)==-1)
        oops("semop:locking",10);
}
release_lock(int semset_id){
    struct sembuf actions[1];
    actions[0].sem_num=1;
    actions[0].sem_flg=SEM_UNDO;    
    actions[0].sem_op=-1;        //如果当前信号量的值小于1,则阻塞。否则减去1。表示要获得由该信号控制的资源
    if(semop(semset_id,actions,1)==-1)
        oops("semop:unlocking",10);
}

管道和socket也包含了锁机制。管道和socket其实也是保存数据的内存段,它将数据从源端复制到目的端。不同的是管道和socket中的锁是由内核,而不是进程来管理的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值