信号量——POSIX 与 System V的接口对比分析

一 POSIX 标准

#include <semaphore.h>

 

sem_t:信号量的数据结构

 

int sem_init (sem_t *sem, int pshared, unsigned int value)

无名信号量(也称为基于内存的信号量)sem初始化,设置共享选项pshared,并指定一个整数类型的初始值为valuepshared参数控制着信号量的类型。如果 pshared的值是0,就表示它是当前里程的局部信号量;否则,其它进程就能够共享这个信号量。

 

sem_t*sem_open(const char *name,int oflag,mode_t mode,unsigned int value);

有名信号量(返回值)的创建和初始化。出错时返回为SEM_FAILED

 

intsem_destroy(sem_t *sem);

释放无名信号量。

 

int sem_close(sem_t *sem);

对应有名信号量的关闭,注意不是销毁,是关闭。

 

int sem_unlink(count char*name);

对应有名信号量的销毁,每个信号都有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把name所指的信号灯从文件系统中删除。也就是要等待最后一个sem_close发生。

 

int sem_wait(sem_t *sem); 

int sem_trywait(sem_t *sem);

对应信号量的P操作。 sem_wait和sem_trywait的差别是:当所指定信号灯的值已是0时,后者并不将调用线程投入睡眠。相反,他返回一个EAGAIN错误。

 

int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem,int *valp);

对应信号量的V操作。

 

注意:posix基于内存的信号灯和posix有名信号灯有一些区别,我们必须注意到这些。

1.sem_open不需要类型与shared的参数,有名信号灯总是可以在不同进程间共享的。

2.sem_init不使用任何类似于O_CREAT标志的东西,也就是说,sem_init总是初始化信号灯的值。因此,对于一个给定的信号灯,我们必须小心保证只调用一次sem_init。

3.sem_open返回一个指向某个sem_t变量的指针,该变量由函数本身分配并初始化。但sem_init的第一个参数是一个指向某个sem_t变量的指针,该变量由调用者分配,然后由sem_init函数初始化。

4.posix有名信号灯是通过内核持续的,一个进程创建一个信号灯,另外的进程可以通过该信号灯的外部名(创建信号灯使用的文件名)来访问它。 posix基于内存的信号灯的持续性却是不定的,如果基于内存的信号灯是由单个进程内的各个线程共享的,那么该信号灯就是随进程持续的,当该进程终止时它也会消失。如果某个基于内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,这要只要该共享内存区存在,该信号灯就存在。
5.基于内存的信号灯应用于线程很麻烦,而有名信号灯却很方便,基于内存的信号灯比较适合应用于一个进程的多个线程。
 

二 System V接口

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

 

sembuf结构体:

struct sembuf {

         shortsem_num;

         shortsem_op;

         shortsem_flg;

};

sem_num是信号量的编号。

sem_op是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用。

sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO。

 

int semget(key_t key, int nsems, intsemflg);

用来创建和访问一个信号量集,key:信号集的名字,nsems:信号集中信号量的个数,semflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样。成功返回一个非负整数,即该信号集的标识码,失败返回-1。

 

int semctl(int semid, int semnum, int cmd,...);

信号集的控制操作,包括销毁、设置信号量的值、读出信号量的值。Semid:由semget返回的信号集标识码。Semnum:信号集中信号量的序号。Cmd:将要采取的动作(有三个可取值)。最后一个参数根据命令不同而不同。

 

int semop(int semid, struct sembuf *sops,unsigned nsops);

用来修改一个信号集,进行PV操作。关键在于理解sembuf结构体。Semid:是该信号量的标识码,也就是semget函数的返回值。Sops:是个指向一个结构数值的指针。Nsops:信号量的个数。

 

三 总结

         在用法方面,SystemV更突出一个集合的概念,因此,可以同时操作多个信号量,例如需要等待2个信号都满足条件才允许运行临界代码时,使用System V的信号更简易,但这不代表POSIX的信号量不能实现。下面以哲学家就餐问题(需要同时等待左右叉子),分别使用两种方法实现,体会其中的差别。

         System V实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
 
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
 
#ifdef  _SEM_SEMUN_UNDEFINED
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *__buf;
};
#endif
 
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)
     
int
wait_1fork(int no,int semid)
{
    //int left = no;
    //int right = (no + 1) % 5;
    struct sembuf sb = {no,-1,0};
    int ret;
    ret = semop(semid,&sb,1);
    if(ret < 0) {
        ERR_EXIT("semop");
    }
    return ret;
}
 
int
free_1fork(int no,int semid)
{
    struct sembuf sb = {no,1,0};
    int ret;
    ret = semop(semid,&sb,1);
    if(ret < 0) {
        ERR_EXIT("semop");
    }
    return ret;
}
 
//这里表明叉子是一个临界资源
#define DELAY (rand() % 5 + 1)
//相当于P操作
void
wait_for_2fork(int no,int semid)
{
    //哲学家左边的刀叉号数
    int left = no;
    //右边的刀叉
    int right = (no + 1) % 5;
 
    //刀叉值是两个
    //注意第一个参数是编号
    struct sembuf buf[2] = {
        {left,-1,0},
        {right,-1,0}
    };
    //信号集中有5个信号量,只是对其中的
    //资源sembuf进行操作
    semop(semid,buf,2);
}
 
//相当于V操作
void
free_2fork(int no,int semid)
{
    int left = no;
    int right = (no + 1) % 5;
    struct sembuf buf[2] = {
        {left,1,0},
        {right,1,0}
    };
    semop(semid,buf,2);
}
 
 
 
void philosophere(int no,int semid)
{
    srand(getpid());
    for(;;) {
    #if 1
        //这里采取的措施是当两把刀叉都可用的时候
        //哲学家才能吃饭,这样不相邻的哲学家就可
        //吃上饭
        printf("%d is thinking\n",no);
        sleep(DELAY);
        printf("%d is hungry\n",no);
        wait_for_2fork(no,semid);//拿到叉子才能吃饭
        printf("%d is eating\n",no);
        sleep(DELAY);
        free_2fork(no,semid);//释放叉子
    #else
        //这段代码可能会造成死锁
        int left = no;
        int right = (no + 1) % 5;
        printf("%d is thinking\n",no);
        sleep(DELAY);
        printf("%d is hungry\n",no);
        wait_1fork(left,semid);
        sleep(DELAY);
        wait_1fork(right,semid);
        printf("%d is eating\n",no);
        sleep(DELAY);
        free_2fork(no,semid);
    #endif
    }
}
 
int
main(int argc,char *argv[])
{
    int semid;
    //创建信号量
    semid = semget(IPC_PRIVATE,5,IPC_CREAT | 0666);
    if(semid < 0) {
        ERR_EXIT("semid");
    }
    union semun su;
    su.val = 1;
    int i;
    for(i = 0;i < 5;++i) {
        //注意第二个参数也是索引
        semctl(semid,i,SETVAL,su);
    }
    //创建4个子进程
    int num = 0;
    pid_t pid;
    for(i = 1;i < 5;++i) {
        pid = fork();
        if(pid < 0) {
            ERR_EXIT("fork");
        }
        if(0 == pid) {
            num = i;
            break;
        }
    }
    //这里就是哲学家要做的事情
    philosophere(num,semid);
    return 0;
}

         POSIX实现(关键在于在尝试获得第二把叉子时,使用的是sem_trywait否则会造成死锁)

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <pthread.h>  
#include <semaphore.h>  
#include <string.h>  
  
#define PEOPLE_NUM 5  
#define THINKING 1  
#define HUNGRY 2  
#define EATING 3  
  
sem_t chopsticks[PEOPLE_NUM];  
pthread_mutex_t mutex;  
  
void *philosopher(void *arg){  
    int id = (int) arg;  
  
    int state = THINKING;  
    int right = (id + 1) % PEOPLE_NUM;  
    int left = (id + PEOPLE_NUM - 1) % PEOPLE_NUM;  
    char ptrState[32];  
  
    while(1){  
        switch(state){  
        case THINKING:  
            usleep(300);  
            state = HUNGRY;  
            strcpy(ptrState,"Thinking before eat");  
            break;  
        case HUNGRY:  
            strcpy(ptrState,"Hungry");  
            if(sem_wait(&chopsticks[left]) == 0){//阻塞状态  
                if(sem_trywait(&chopsticks[right]) == 0){//非阻塞  
                    strcpy(ptrState,"I will Eating");  
                    state = EATING;  
                }else{  
                    state = THINKING;  
                    strcpy(ptrState,"I have not chopsticks");  
                    sem_post(&chopsticks[left]);//释放请求的得到的left筷子  
                    printf("Philosopher right chopsticks is busy,right=%d,thread id is %d\n",right,id);  
                }  
            }else{  
                printf("Philosopher left chopsticks is busy,left=%d,thread id is %d\n",left,id);//这句话由于上面被阻塞永远不会输出  
            }  
            break;  
        case EATING:  
            printf("Philosopher fetch left and right chopsticks: (%d,%d), threadid is %d\n",left,right,id);  
            sem_post(&chopsticks[left]);  
            sem_post(&chopsticks[right]);  
            printf("Philosopher release left and right chopsticks: (%d,%d), threadid is %d\n",left,right,id);  
            usleep(500);  
            state = THINKING;  
            strcpy(ptrState,"Thinking after eat");  
            break;  
        }  
        pthread_mutex_lock(&mutex);  
        printf("Philosopher is %s, thread id is %d\n",ptrState,id);  
        pthread_mutex_unlock(&mutex);  
        usleep(1000);  
    }  
  
    pthread_exit((void*)0);  
}  
  
int main(){  
    pthread_t tid[PEOPLE_NUM];  
    int i;  
    pthread_mutex_init(&mutex,NULL);  
    for(i = 0 ; i < PEOPLE_NUM ; i ++){  
        sem_init(&chopsticks[i],0,1);  
    }  
    for(i = 0 ; i < PEOPLE_NUM ; i ++){  
        pthread_create(&tid[i],NULL,philosopher,(void*)i);  
    }  
    for(i = 0 ; i < PEOPLE_NUM ; i ++){  
        pthread_join(tid[i],NULL);  
    }  
    return 0;  
}  

         另外,也给出使用POSIX 互斥量的方法解决(与POSIX信号量,有相似之处)

 

#include <stdio.h>  
#include <stdlib.h>  
#include <memory.h>  
#include <pthread.h>  
#include <errno.h>  
#include <math.h>  
//筷子作为mutex  
pthread_mutex_t chopstick[6] ;  
void *eat_think(void *arg)  
{  
    char phi = *(char *)arg;  
    int left,right; //左右筷子的编号  
    switch (phi){  
        case 'A':  
            left = 5;  
            right = 1;  
            break;  
        case 'B':  
            left = 1;  
            right = 2;  
            break;  
        case 'C':  
            left = 2;  
            right = 3;  
            break;  
        case 'D':  
            left = 3;  
            right = 4;  
            break;  
        case 'E':  
            left = 4;  
            right = 5;  
            break;  
    }  
  
    int i;  
    for(;;){  
        usleep(3); //思考  
        pthread_mutex_lock(&chopstick[left]); //拿起左手的筷子  
        printf("Philosopher %c fetches chopstick %d\n", phi, left);  
        if (pthread_mutex_trylock(&chopstick[right]) == EBUSY){ //拿起右手的筷子     
            pthread_mutex_unlock(&chopstick[left]); //如果右边筷子被拿走放下左手的筷子  
            continue;  
        }  
          
    //  pthread_mutex_lock(&chopstick[right]); //拿起右手的筷子,如果想观察死锁,把上一句if注释掉,再把这一句的注释去掉  
        printf("Philosopher %c fetches chopstick %d\n", phi, right);  
        printf("Philosopher %c is eating.\n",phi);  
        usleep(3); //吃饭  
        pthread_mutex_unlock(&chopstick[left]); //放下左手的筷子  
        printf("Philosopher %c release chopstick %d\n", phi, left);  
        pthread_mutex_unlock(&chopstick[right]); //放下左手的筷子  
        printf("Philosopher %c release chopstick %d\n", phi, right);  
  
    }  
}  
int main(){  
    pthread_t A,B,C,D,E; //5个哲学家  
  
    int i;  
    for (i = 0; i < 5; i++)  
        pthread_mutex_init(&chopstick[i],NULL);  
    pthread_create(&A,NULL, eat_think, "A");  
    pthread_create(&B,NULL, eat_think, "B");  
    pthread_create(&C,NULL, eat_think, "C");  
    pthread_create(&D,NULL, eat_think, "D");  
    pthread_create(&E,NULL, eat_think, "E");  
  
    pthread_join(A,NULL);  
    pthread_join(B,NULL);  
    pthread_join(C,NULL);  
    pthread_join(D,NULL);  
    pthread_join(E,NULL);  
    return 0;  
}

参考文献

http://blog.csdn.net/jasenwan88/article/details/7766808

http://blog.csdn.net/acceptedxukai/article/details/8307247

http://www.oschina.net/code/snippet_724028_36857

http://blog.chinaunix.net/uid-26746982-id-3388652.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值