信号量 - 多进程同步编程(六)

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

1、特点

  1. 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存

  2. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

  3. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

  4. 支持信号量组。

2、原型

最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。

Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
3 int semget(key_t key, int num_sems, int sem_flags);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
5 int semop(int semid, struct sembuf semoparray[], size_t numops);  
6 // 控制信号量的相关信息
7 int semctl(int semid, int sem_num, int cmd, ...);

semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。

semop函数中,sembuf结构的定义如下:

1 struct sembuf 
2 {
3     short sem_num; // 信号量组中对应的序号,0~sem_nums-1
4     short sem_op;  // 信号量值在一次操作中的改变量
5     short sem_flg; // IPC_NOWAIT, SEM_UNDO
6 }

其中 sem_op 是一次操作中的信号量的改变量:

  • sem_op > 0,表示进程释放相应的资源数,将 sem_op 的值加到信号量的值上。如果有进程正在休眠等待此信号量,则换行它们。

  • sem_op < 0,请求 sem_op 的绝对值的资源。

    • 如果相应的资源数可以满足请求,则将该信号量的值减去sem_op的绝对值,函数成功返回。
    • 当相应的资源数不能满足请求时,这个操作与sem_flg有关。
      • sem_flg 指定IPC_NOWAIT,则semop函数出错返回EAGAIN
      • sem_flg 没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:
        1. 当相应的资源数可以满足请求,此信号量的semncnt值减1,该信号量的值减去sem_op的绝对值。成功返回;
        2. 此信号量被删除,函数smeop出错返回EIDRM;
        3. 进程捕捉到信号,并从信号处理函数返回,此情况下将此信号量的semncnt值减1,函数semop出错返回EINTR
  • sem_op == 0,进程阻塞直到信号量的相应值为0:

    • 当信号量已经为0,函数立即返回。
    • 如果信号量的值不为0,则依据sem_flg决定函数动作:
      • sem_flg指定IPC_NOWAIT,则出错返回EAGAIN
      • sem_flg没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:
        1. 信号量值为0,将信号量的semzcnt的值减1,函数semop成功返回;
        2. 此信号量被删除,函数smeop出错返回EIDRM;
        3. 进程捕捉到信号,并从信号处理函数返回,在此情况将此信号量的semncnt值减1,函数semop出错返回EINTR

semctl函数中的命令有多种,这里就说两个常用的:

  • SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
  • IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。

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

// 联合体,用于semctl初始化
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// 初始化信号量
int init_sem(int sem_id, int value)
{
    union semun tmp;
    tmp.val = value;
    if(semctl(sem_id, 0, SETVAL, tmp) == -1)
    {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P操作:
//    若信号量值为1,获取资源并将信号量值-1
//    若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V操作:
//    释放资源并将信号量值+1
//    如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// 删除信号量集
int del_sem(int sem_id)
{
    union semun tmp;
    if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
    {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}


int main()
{
    int sem_id;  // 信号量集ID
    key_t key;
    pid_t pid;

    // 获取key值
    if((key = ftok(".", 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // 创建信号量集,其中只有一个信号量
    if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
    {
        perror("semget error");
        exit(1);
    }

    // 初始化:初值设为0资源被占用
    init_sem(sem_id, 0);

    if((pid = fork()) == -1)
        perror("Fork Error");
    else if(pid == 0) /*子进程*/
    {
        sleep(2);
        printf("Process child: pid=%d\n", getpid());
        sem_v(sem_id);  /*释放资源*/
    }
    else  /*父进程*/
    {
        sem_p(sem_id);   /*等待资源*/
        printf("Process father: pid=%d\n", getpid());
        sem_v(sem_id);   /*释放资源*/
        del_sem(sem_id); /*删除信号量集*/
    }
    return 0;
}

1、信号量
特点:
1、信号量是一个计数器,用于多进程对共享数据对象的访问。
2、信号量的初始值是任一正数,说明有多少个共享资源单位可供共享应用。(常用的信号量形式:初始值为1,即某一资源某一时刻只能给单一进程占有。称为二元信号量或双态信号量)。

控制过程:
①、测试控制该资源的信号量。
②、若信号量的值为正,则进程可以使用该资源。进程将信号量的值减1,表示它使用了一个资源单位。
③、若信号量的值为0,则进程进入休眠状态,直到进程信号量大于1.进程被唤醒,再返回①。
④、当进程不在使用共享资源的时候,该信号量的值+1。若其它有进程等待此信号量,则唤醒它。

应用:
常用于进程的同步,例如多进程项目的log打印或写文件。
2、信号量集
信号量集:信号量的集合
当创建一个信号量集时,要指定该集合中信号量的数量
创建信号量集(semget)与对其赋初值(semctl)应一起,不要分开。

a、创建信号量 或 获取现在存的信号量:
int semget(key_t key,int nsems,int flag)
key:键值
nsems:表示信号量的数量
flag :同msgget
b、操作一个现有的信号量
int semctl(int semid,int semnum,int cmd,.../* union semun arg */)
semid:semget返回的信号量ID
semnum: [0] -- [nsems-1]信号量集合,即有nsems个信号量
c、操作信号量资源
int semop(int semid,struct sembuf semoparray[],size_t nops) //nops 操作几个信号量

3、实例
3.1、semget、semctl
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
 
#define    NR     5
#define    OP_ALL
 
int main(void)
{
    int i,ret,semid;
    //NR:信号量数组中的信号量个数
    semid=semget(0x555,NR,IPC_CREAT|0644);//0x555键值,随便写的一个全局的键值
 
    if(semid==-1)
    {
      perror("semget");
      return 1;
    }
    printf("semid:%d\n",semid);
//--------------设置信号量值------------------------
#ifndef OP_ALL//逐一赋值
    for(i=0;i<NR;i++)
    {
       ret=semctl(semid,i,SETVAL,1);
       if(ret==-1)    return 2;
    }
#else//整体赋值
    unsigned short  val[NR]={1,1,1,1,1};
    ret=semctl(semid,0,SETALL,val);
    if(ret==-1)        return 2;//error
#endif //
//---------------get semval-------------------------
#ifndef OP_ALL//逐一获取
    for(i=0;i<NR;i++)
    {
       ret=semctl(semid,i,GETVAL);
       if(ret==-1)     return 3;
       printf("%dth==>val:%d\n",i,ret);
    }
#else//整体获取
    unsigned short  temp[NR];
    ret=semctl(semid,0,GETALL,temp);
    if(ret==-1)        return 3;//error
    for(i=0;i<NR;i++)
        printf("%dth==>val:%d\n",i,temp[i]);
#endif //
//---------------------------------------------------
 
    getchar();
    printf("===============del sem array==========\n");
    semctl(semid,0,IPC_RMID);//删除信号量
    return 0;
}
3.2、父子进程同步打印字符(semop应用)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
 
#define    NR     1
//申请资源
void p_lock(int semid);
//释放资源
void v_unlock(int semid);
 
int main(void)
{
    pid_t pid;
    int   ret,semid;
 
    semid=semget(0x555,NR,IPC_CREAT|0644);
    if(semid==-1)   return 11;
    ret=semctl(semid,0,SETVAL,10);
    if(ret==-1)     return 22;
//------------------------------------------
    pid=fork();
    if(pid==-1)   return 1;
    else if(pid==0)  //child
    {
       char i,*str="1234567890";
       for(i=0;str[i];i++)
       {
          p_lock(semid);
          write(STDOUT_FILENO,str+i,1);
          usleep(500000);
          v_unlock(semid);
       }
       exit(0);
    }
    //parent
    char i,*str="abcdefghij";
    for(i=0;str[i];i++)
    {
       p_lock(semid);
       write(STDOUT_FILENO,str+i,1);
       sleep(2);
       v_unlock(semid);
    }
    wait(NULL);
    putchar('\n');
    return 0;
}
void p_lock(int semid)
{
   struct sembuf buf={.sem_num=0,.sem_op=-10};
   semop(semid,&buf,1);
}
void v_unlock(int semid)
{
   struct sembuf buf={.sem_num=0,.sem_op= 10};
   semop(semid,&buf,1);
}

3.3、文件同步写
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
 
#include <sys/ipc.h>
#include <sys/sem.h>
 
#define     TIMES    1000000
 
static int  semid;
 
void write_file(int fd);
 
void unlock(void);
void lock(void);
 
 
int main(void)
{
    int ret,fd,val=0;
    pid_t pid;
 
    fd=open("./txt",O_RDWR|O_TRUNC|O_CREAT,0644);
    if(fd==-1)   return 1;
 
    pwrite(fd,&val,sizeof(int),0);
//----------------------------------------
    semid=semget(0x555,1,IPC_CREAT|0644);//创建一个信号量
    if(semid==-1)   return    11;
    ret=semctl(semid,0,SETVAL,1);//设置一个信号量,资源为1
    if(ret==-1)     return    22;
//-----------------------------------------
    pid=fork();
    if(pid==-1)   return 2;//error
    else if(pid==0)  //child
    {
      printf("child start......\n");
      write_file(fd);
      printf("child write file over\n");
      close(fd);
      exit(0);
    }
    //parent
    printf("parent write file\n");
    write_file(fd);
    wait(NULL);
 
    pread(fd,&val,sizeof(int),0);
    printf("====>val:%d\n",val);
 
    close(fd);
    return 0;
}
void write_file(int fd)
{
    int i,val;
 
    for(i=0;i<TIMES;i++)
    {
//lock
       lock();
       pread(fd,&val,sizeof(int),0);
       val+=1;
       pwrite(fd,&val,sizeof(int),0);
       unlock();
//unlock
    }
}
 
void lock(void)
{
    struct sembuf  buf={.sem_op=-1,.sem_num=0};
    semop(semid,&buf,1);
}
void unlock(void)
{
    struct sembuf  buf={.sem_op=1,.sem_num=0};
    semop(semid,&buf,1);
}
3.4、等0操作
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
 
#define    NR       1
 
void v_unlock(int semid);
void p_lock(int semid);
 
int main(void)
{
    int i,ret,semid;
    pid_t  pid;
 
    semid=semget(0x666,NR,IPC_CREAT|0644);
    if(semid==-1)   return 1;
    ret=semctl(semid,0,SETVAL,10);
    if(ret==-1)     return 2;
 
    pid=fork();
    if(pid==-1)    return 1;
    else if(pid==0) //child
    {
       while(1)
       {
          for(i=0;i<10;i++)
          {
             p_lock(semid);
             printf("===child:%d=====\n",i);
             sleep(1);
          }
       }
    }
    //parent
    struct sembuf zero={.sem_num=0,.sem_op=0};
    while(1)
    {
       //wait zero
       semop(semid,&zero,1);
       printf("==========parent=========\n");
       v_unlock(semid);
    }
 
 
    return 0;
}
void v_unlock(int semid)
{
     struct sembuf buf={.sem_num=0,.sem_op=10};
     semop(semid,&buf,1);
}
void p_lock(int semid)
{
     struct sembuf buf={.sem_num=0,.sem_op=-1};
     semop(semid,&buf,1);
}


3.5、栅栏程序模型
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
 
#include <signal.h>
 
#define     NR      5
 
static int semid;
void sigfunc(int signo)
{
   if(signo!=SIGINT)  return;
   semctl(semid,0,IPC_RMID);
   exit(0);
}
void do_work(int index);
void p_lock(int index);
 
int main(void)
{
    int    i;
    pid_t  pid;
 
    signal(SIGINT,sigfunc);//is error
 
    //------create sem-----------------
    //对应的信号量用控制对应的子进程 最后一个用于父进程控制周期
    semid=semget(0x555,NR+1,IPC_CREAT|0644);
    if(semid==-1)
    {
       perror("semget");
       return 1;
    }
    unsigned short val[NR+1]={0};
    i=semctl(semid,0,SETALL,val);
    if(i==-1)
    {
       perror("semctl");
       return 2;
    }
    //-----------------------------------
    for(i=0;i<NR;i++)
    {
       pid=fork();
       if(pid==-1)   return 1;
       else if(pid==0)//child
       {
          do_work(i);
          exit(0);
       }
    }
    //---------------给定子进程资源---------------
    struct sembuf ar[NR+1],zero={.sem_num=NR,.sem_op=0};
    for(i=0;i<NR;i++)
    {
       ar[i].sem_op =1;
       ar[i].sem_num=i;
    }
    ar[NR].sem_num=NR;
    ar[NR].sem_op =NR;
    //-----------------------------------
    while(1)
    {
       semop(semid,&zero,1);//等0操作
       printf("==========================\n");
 
       semop(semid,ar,NR+1);
    }
 
    while(wait(NULL)!=-1)
       ;//empty
    return 0;
}
 
void do_work(int index)
{
    int sec;
    srand(getpid());
    while(1)
    {
        p_lock(index);
        sec=rand()%6+1;
        sleep(sec);
        printf("%d:[%d] sleep:%d\n",index,getpid(),sec);
        //通知父进程已跑完
        p_lock(NR);
    }
}
void p_lock(int index)
{
    struct sembuf  s={.sem_num=index,.sem_op=-1};
    if(semop(semid,&s,1)==-1)
    {
       perror("semop");
       exit(1);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值