信号量

概念

  提供不同进程间或给定的一个特定进程的不同线程间的同步手段。

  信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。

  有名信号量,是根据外部名字标识,通常指代文件系统中的某个文件,但不要求真正存放在文件系统的某个文件中(如果信号量的实现用到了映射文件);随内核持续,可用于进程或线程间的同步

  基于内存的信号量,放在共享内存中,基于内存的信号灯,同步多线程时,可以放到该多线程所属进程空间里;如果是同步多进程,那就需要把信号灯放入到共享内存中(方便多个进程访问)。随进程持续

  有名信号灯和基于内存的信号灯,具体区别体现在创建和销毁两个函数。有名信号灯使用sem_open和sem_close函数。基于内存的信号灯使用sem_init和sem_destroy函数。sem_init的参数可以控制是同步多线程,还是多进程;且该函数只能调用1次,因为调用后信号灯就存在了( 内存指针存在)。一般,使用基于内存的信号灯同步同进程多线程,使用有名信号灯同步多进程

 创建信号量:调用者指定一个初始值,对于二值信号量来说通常是1

 等待一个信号量:该操作会测试这个信号量,如果其值小于等于0那么就等待阻塞,一旦值大于0就将他减一,访问同一信号量的其它线程或进程,在while测试其值然后将他减一(如果该值大于0)这两个步骤需为原子操作,这就使信号量成为内核系统调用,使其成为原子操作就变得容易

 挂出一个信号量:将信号量的值加一

有名信量

  一个信号量的状态可能包含在它本身的sem_t数据类型中,还包含其他的信息(如:文件描述符),知道该标识符的任何进程都可以访问该信号量,它的整数标识符只是告诉内核具体引用哪个信号量

  不同进程(无论彼此有无亲缘关系)总是能够访问同一个有名信号量,只要他们在调用sem_open时指定相同的名字就行,即使对于某个特定的名字sem_open调用在每个进程中可能返回不同的指针,使用该指针的信号量函数(sem_post,sem_wait)引用的任然是同一个信号量

  父子进程共享一个全局信号量

sem_t *mutex;//全局变量

mutex=sem_open(...);

if((cpid=fork())==0)
{
    sem_wait(mutex);
}

sem_post(mutex);

sem_open

  创建并初始化信号灯,如果存在就返回存在的信号灯。

#include <semaphore.h>
sem_t * sem_open(const char * name,int oflag,mode_t mode,unsigned int value);
sem_t * sem_open(const char * name,int oflag);

  name是给信号灯指定一个名字。oflag的值为O_CREAT,表示如果信号灯不存在,创建信号灯;为O_CREAT|O_EXCL,如果信号灯不存在报错。后面两个参数,只有新建信号灯时使用。mode为信号灯的权限(0644),value为信号灯的值。
  返回值:成功时,返回信号灯的指针,错误返回SEM_FAILED
  oflag:

  1. O_CREAT:若文件不存在则创建,创建文件时设定第三个参数和第四个参数,指定访问权限和初始值,初始值不能超过SEM_VALUE_MAX(通常为32767),二值信号量初始值通常为1,计数信号量初始值往往大于1,如果指定O_CREATE而没有指定O_EXCL当所需信号量尚未存在初始化,所需信号量已存在并不出错
  2. O_EXCL:将测试文件是否存在和创建文件组成原子操作,如果所需信号量已存在下指定O_CREATE|O_EXCL出错(这样的设计就不会出现两个进程同时创建覆盖的后果了);
  3.  O_NONBLOCK:如果path引用的是FIFO、块特殊文件或字符特殊文件,将I/O操作设置为非阻塞状态;

  默认都具备读写权限不需要指定O_RNONLY 和O_WRONLY,因为信号量的挂出与等待操作都要改变信号量的值,不具备读写权限的信号量会导致sem_open返回EACCES(访问权限不符合)

sem_close 

关闭引用信号灯,信号灯引用计数减1。
#include <semaphore.h>
int sem_close(sem_t * sem)
参数:sem为信号灯的指针
返回值:成功时,返回0,失败,-1
注:每个信号灯有一个引用计数器记录当前打开次数.关闭一个信号灯并没有将它从系统中删除因为他是随内核持续的,而是信号灯引用计数减1,进程的终止都会引发此操作(自愿终止exit或非自愿终止),不显示调用也可以
可以为每个线程调用该函数,这一操作终止进程时会自动发生,然而删除一个有名信号量必须显示完成

sem_unlink

信号灯引用计数为大于0时,name从系统中删除,而其信号量的析构要等到最后一个sem_close发生。
#include <semaphore.h>
int sem_close(const char *name)
参数:name为信号灯的外部名字
返回值:成功时,返回0,失败,-1,需要显示调用

sem_wait/sem_try_wait

等待共享资源,信号灯值为0就睡眠直到该值大于0时再将它减一,信号灯值大于0,就使用共享资源,信号灯值减1。sem_trywait当信号灯值为0时,不睡眠,返回EAGAIN;sem_wait检测死锁并会返回EDEADLK,过早返回错误为EINTR.
#include <semaphore.h>
int sem_wait(sem_t *sem)
int sem_trywait(sem_t *sem)
参数:sem为信号灯指针
返回值:成功时,返回0,失败,-1
当持有某个信号量的锁的进程没有释放它就终止时,内核并不给该信号量解锁

sem_getvalue

  获得信号灯当前值

#include <semaphore.h>
int sem_getvalue(sem_t *sem,int *valp)
参数:sem为信号灯指针,valp为信号灯的值
返回值:成功时,返回0,失败,-1,valp返回信号量当前值,如果当前信号量以上锁,返回0或返回某个负数,其绝对值等于等待该信号量解锁的线程数

sem_post

#include <semaphore.h>
int sem_post(sem_t *sem)
参数:sem为信号灯指针
返回值:成功时,返回0,失败,-1
任何线程都可以挂出一个信号,即使当前没有线程在该信号上阻塞,然而如果某个线程调用pthread_cond_signal,当时没有任何线程阻塞在pthread_cond_wait上时,那么发送相应的条件变量的信号将丢失

互斥锁,信号量条件变量区别

  1. 互斥锁必须总是由锁住他的线程解锁,信号量的挂出不必由执行过它的等待操作的同一线程执行
  2. 互斥锁要么被锁住要没被解开
  3. 既然信号量有一个与之关联的状态(它的计数值)那么信号量的挂出总是被记住,然而向一个条件变量上发送信号时,如果没有线层等待该条件变量那么信号将丢失

总结:

  当某个进程持有信号量的锁时进程没有释放他就终止,内核并不给信号量解锁,这与锁不同,当进程持有某个记录的锁没有释放就终止,内核自动释放。

  能够从信号处理程序中安全调用的唯一函数时sem_post,互斥锁是为上锁而优化,条件变量为等待而优化,信号量即可用于上锁,也可用于等待,01信号量就相当于互斥锁

  sem_wait在一个信号灯上等待(以锁住一个资源或等待一个时间),如果信号灯计数比0大,sem_wait函数将计数器减一并马上返回,否则,线程阻塞,一个阻塞的线程通过sem_post来发布一个信号灯(开锁一个资源或等待一个线程),

如果一个以上的线程在信号灯上等待,sem_post将唤醒其中一个(优先级最高的或最早等待的线程),如果没有线程在等待,信号灯计数器被加一。

   假如有个代码段,期望最多有两个线程同时在里面执行(其他线程必须等待),可以使用一个信号灯而不需要附加任何状态,初始化信号灯值为2,然后再代码段开始调用sem_wait,结尾处调用sem_post,然后两个线程可以再信号登上等待而不被阻塞,

但是第三个线程将发现信号灯计数器为0,并且阻塞,每个系那成退出代码区时,他信号灯释放一个等待吸纳城(如果有的话恢复计数器)。

   不同进程(不管是否彼此有无亲缘关系),他们都可以访问同一个信号灯,只是需要在sem_open的时候传入的名字是一样就行。在有亲缘关系时,Posix中的fork如是描述,在父进程中打开的任何信号灯,仍应在子进程中打开。 

生产者与消费者问题:

#include "unpipc.h"

#define BUFF 10
#define SEM_MUTEX "/tmpmutex"
#define SEM_NEMPTY "/tmpnempty"
#define SEM_NSTORED "/tmpnstored"

struct shared
{
    int buffer[BUFF];
    sem_t *mutex,*nempty,*nstored;
}shared;

void *produce(void *arg)
{
    int i;
    for(i=0;i<BUFF;++i)
    {
        sem_wait(shared.nempty);
        sem_wait(shared.mutex);

        shared.buffer[i%BUFF]=i;

        sem_post(shared.mutex);
        sem_post(shared.nstored);
    }
    return NULL;
}

void *consume(void *arg)
{
    int i;
    for(i=0;i<BUFF;++i)
    {
        sem_wait(shared.nstored);//这两个wait交换会造成死锁
        sem_wait(shared.mutex);

        printf("shared.buffer[%d]= %d\n",i,shared.buffer[i%BUFF]);

        sem_post(shared.mutex);
        sem_post(shared.nempty);
    }
    return NULL;
}

int main()
{
    pthread_setconcurrency(2);
    pthread_t pid_consume,pid_produce;

    /*每个信号量都需要正确的初始化,如果先前建立的信号里因本程序终止没有
     * 删除,那么,可以在sem_open之前调用sem_unlink,并忽略任何错误,也
     * 可以指定O_EXCL,检查是否返回EEXIST错误,若是,调用sem_unlink,并
     * 且再次调用sem_open。如果想验证本程序只有一个副本在运行,可以调用
     * fcntl文件锁
     */
    shared.mutex=sem_open(SEM_MUTEX,O_CREAT|O_EXCL,FILE_MODE,1);
    shared.nempty=sem_open(SEM_NEMPTY,O_CREAT|O_EXCL,FILE_MODE,BUFF);
    shared.nstored=sem_open(SEM_NSTORED,O_CREAT|O_EXCL,FILE_MODE,BUFF);

    pthread_create(&pid_produce,NULL,produce,NULL);
    pthread_create(&pid_consume,NULL,consume,NULL);

    pthread_join(pid_produce,NULL);
    pthread_join(pid_consume,NULL);

    sem_unlink(SEM_MUTEX);
    sem_unlink(SEM_NEMPTY);
    sem_unlink(SEM_NSTORED);

    exit(0);
}

基于内存的信号量

#include <semaphore.h>

int sem_init(sem_t * sem,int shared,unsigned int value);
int sem_destroy(sem_t * sem)
参数:sem为指向应用程序必须分配的sem_t变量,shared是指同步多线程还是多进程(0:同一进程间的线程共享;其他:进程间共享,该信号量必须放在在某个类型的共享内存中,而即将使用他的所有进程都要能访问共享内存),value为信号量值
返回值:成功时,不返回0,失败时,返回-1

注意:

  1.   必须保证该函数只调用一次,对一个已经初始化的信号量调用sem_init结果是未定义的。
  2.   sem_open与sem_init:前者返回一个指向某个sem_t变量的指针,该变量由sem_open函数本身分配并初始化;后者的第一个参数时指向某个sem_t变量的指针,该变量由调用者分配,然后由sem_int初始化
  3.   一个基于内存的信号量,只有sem_init的参数指向的位置可用于访问该信号量,使用他的sem_t数据类型副本访问时为定义的结果
  4.       只要某个基于内存的信号量的内存区保持有效,该信号量就一直存在,随进程持续,进程终止其消失
  5.       基于内存的信号量在不同进程间共享,该信号量必须放在共享内存区中,只要该共享内存存在,信号量就一直存在
  6.       fork的子进程通常不共享父进程内存空间,所以不能用sem_init
  7.       在父进程中打开的信号量在子进程中仍然打开
#include "unpipc.h"
#include <pthread.h>
#include <string.h>

#define BUFF 10

struct shared
{
    struct buff
    {
        char data[BUFFSIZE];
        size_t n;
    }buffer[BUFF];
    sem_t mutex,nempty,nstored;
}shared;

int fd;

void *produce(void *arg)
{
    int i=0;
    while(1)
    {
        //printf("fd is:%d\n",fd);
        sem_wait(&shared.nempty);

        sem_wait(&shared.mutex);
        //如果数据缓冲区要是在一个连表上维护,此处是链表中移除某个缓冲区的
        //地方,把该操作放在临界区是避免生产者消费者对链表操作发生冲突
        sem_post(&shared.mutex);

        shared.buffer[i].n=read(fd,shared.buffer[i].data,BUFFSIZE);
        if(shared.buffer[i].n==0)//读取结束
        {
            sem_post(&shared.nstored);
            return NULL;
        }

        if(++i>=BUFF)
            i=0;//循环缓冲

        sem_post(&shared.nstored);
    }
    return NULL;
}

void *consume(void *arg)
{
    int i=0;
    while(1)
    {
        sem_wait(&shared.nstored);

        sem_wait(&shared.mutex);
        //同上
        sem_post(&shared.mutex);

        if(shared.buffer[i].n==0)
            return NULL;

        write(1,shared.buffer[i].data,shared.buffer[i].n);
        if(++i>BUFF)
            i=0;

        sem_post(&shared.nempty);
    }
    return NULL;
}

int main()
{
    printf("请输入文件名\n");
    char pathname[100];
    fgets(pathname,100,stdin);
    int len=strlen(pathname);
    if(pathname[len-1]=='\n')
        pathname[len-1]='\0';
    fd=open(pathname,O_RDONLY);
    //printf("pathname:%s",pathname);
    //printf("fd is main: %d\n",fd);

    sem_init(&shared.mutex,0,1);
    sem_init(&shared.nempty,0,BUFF);
    sem_init(&shared.nstored,0,0);

    pthread_setconcurrency(2);

    pthread_t pid_produce,pid_consume;

    pthread_create(&pid_consume,NULL,consume,NULL);
    pthread_create(&pid_produce,NULL,produce,NULL);
    //pthread_create(&pid_consume,NULL,consume,NULL);

    pthread_join(pid_produce,NULL);
    pthread_join(pid_consume,NULL);

    sem_destroy(&shared.mutex);
    sem_destroy(&shared.nempty);
    sem_destroy(&shared.nstored);

    exit(0);
}

代码github:https://github.com/tianzengBlog/test/tree/master/ipc2/sem

信号量的限制

  在<unistd,h>中定义,也可运行时通过sysconf获取

  SEM_NSEMS_MAX:一个进程可以最大打开的信号量数(posix要求至少为256)

  SEM_VALUE_MAX:一个信号量的最大值(posix至少要求为32767)

转载于:https://www.cnblogs.com/tianzeng/p/9314463.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值