Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1416641
  • 博文数量: 1334
  • 博客积分: 645
  • 博客等级: 上士
  • 技术积分: 5762
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-25 16:56
文章分类

全部博文(1334)

文章存档

2014年(108)

2013年(1059)

2012年(169)

分类:

2012-12-07 18:17:10

原文地址:进程间同步之--信号量 作者:zimang

信号量分有名和无名信号量。它们的区别和管道及命名管道的区别类似。有名信号量要求创建一个文件,而无名信号量则直接保存在内存中。

一,Posix信号量
Po***信号量接口总结(见下图):
上面一行是有名信号量,可于fifo相类比,其值保存在文件中,可用于进程和线程同步;
下面一行是无名信号量,可与pipe相类比,其值保存在内存中,可用于进程和线程同步;
中间部分,是两者的公用接口。

  1. sem_open()                                sem_close(),sem_unlink()  //有名信号量  
  2.          \ |sem_wait(),sem_post()       |/  
  3.          / |sem_trywait(),sem_getvalue()|\sem_destroy()  //无名信号量  
  4. sem_init()  



1.公共接口

1.1 接口函数说明

#include
int sem_wait(sem_t *sem);
    测试所指定信号量的值,它的操作是原子的。
    若sem>0,那么它减1并立即返回。
    若sem==0,则睡眠直到sem>0,此时立即减1,然后返回。   

int sem_trywait(sem_t *sem);
    其他的行为和sem_wait一样,除了:
    若sem==0,不是睡眠,而是返回一个错误EAGAIN。       

int sem_post(sem_t *sem);
    把指定的信号量sem的值加1;
    呼醒正在等待该信号量的任意线程。
   
int sem_getvalue(sem_t *sem, int *sval);
    取回信号量sem的当前值,把该值保存到sval中。
    若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回两种值:
        1) 返回0
        2) 返回阻塞在该信号量上的进程或线程数目
    linux采用返回的第一种策略。

注意:在这些函数中,只有sem_post是信号安全的函数,它是可重入函数。

1.2 接口使用的一般流程

sem_init(&sem);
sem_wait(&sem);
critical area;
sem_post(&sem);
remainder area

2.无名信号量
    无名信号量是保存在变量类型为sem_t的内存中。

int sem_init(sem_t *sem, int pshared, unsigned int value);
    1)pshared==0 用于同一多线程的同步;
    2)若pshared>0 用于多个进程间的同步,此时sem必须放在共享内存中。

int sem_destroy(sem_t *sem);
    只能销毁由sem_init初始化的信号量,否则后果不可预料也。

例1:
    多线程使用信号量的简单例子:

  1. /* 
  2.  * simple_sem_app.c 
  3.  */  
  4. #include "all.h"  
  5.   
  6. /* 每个字符输出的间隔时间 */  
  7. #define TEN_MILLION 5000000L  
  8. #define BUFSIZE 1024  
  9.   
  10. void *threadout(void *args);  
  11.   
  12. int main(int argc, char *argv[])  
  13. {  
  14.     int error;  
  15.        int i;  
  16.        int n;  
  17.     sem_t semlock;  
  18.        pthread_t *tids;  
  19.      
  20.        if (argc != 2) {  
  21.            fprintf (stderr, "Usage: %s numthreads\n", argv[0]);  
  22.               return 1;  
  23.        }    
  24.        n = atoi(argv[1]);  
  25.        tids = (pthread_t *)calloc(n, sizeof(pthread_t));  
  26.        if (tids == NULL) {  
  27.            perror("Failed to allocate memory for thread IDs");  
  28.            return 1;  
  29.        }    
  30.        if (sem_init(&semlock, 0, 1) == -1) {  
  31.            perror("Failed to initialize semaphore");  
  32.            return 1;  
  33.        }    
  34.        for (i = 0; i < n; i++) {  
  35.            if (error = pthread_create(tids + i, NULL, threadout, &semlock)) {  
  36.                fprintf(stderr, "Failed to create thread:%s\n", strerror(error));  
  37.                   return 1;  
  38.           }  
  39.     }  
  40.        for (i = 0; i < n; i++) {  
  41.            if (error = pthread_join(tids[i], NULL)) {  
  42.                fprintf(stderr, "Failed to join thread:%s\n", strerror(error));  
  43.                  return 1;  
  44.               }  
  45.     }  
  46.     return 0;  
  47. }  
  48.   
  49. void *threadout(void *args)  
  50. {  
  51.     char buffer[BUFSIZE];  
  52.        char *c;  
  53.        sem_t *semlockp;  
  54.        struct timespec sleeptime;  
  55.      
  56.        semlockp = (sem_t *)args;  
  57.        sleeptime.tv_sec = 0;  
  58.        sleeptime.tv_nsec = TEN_MILLION;  
  59.      
  60.        snprintf(buffer, BUFSIZE, "This is thread from process %ld\n",  
  61.                (long)getpid());  
  62.        c = buffer;  
  63.        /****************** entry section *******************************/  
  64.        while (sem_wait(semlockp) == -1)  
  65.            if(errno != EINTR) {  
  66.                fprintf(stderr, "Thread failed to lock semaphore\n");  
  67.                  return NULL;  
  68.               }  
  69.        /****************** start of critical section *******************/  
  70.        while (*c != '\0') {  
  71.               fputc(*c, stderr);  
  72.               c++;  
  73.               nanosleep(&sleeptime, NULL);  
  74.        }  
  75.        /****************** exit section ********************************/  
  76.        if (sem_post(semlockp) == -1)  
  77.               fprintf(stderr, "Thread failed to unlock semaphore\n");  
  78.        /****************** remainder section ***************************/  
  79.        return NULL;  
  80. }  


说明:该例子来自于usp。
    可以把sem_wait和sme_post调用去掉,看看效果,可以看到出现了交叉输出的情况。
    nanosleep调用只是为了让输出的效果更明显,没有其他意义。

更多的例子见mypxsem/prodcons2-4.c


3. 有名信号量
有名信号量是把信号量的值保存在文件中,所以它可以用于线程也可以用于进程间的同步。
如下面的形式:
  1. sem_t *mutex;  
  2. ...  
  3. mutex = sem_open(pathname, O_CREAT | O_EXCL, FILE_MODE, 0);     
  4. if ((childpid = fork()) == 0) {  
  5.     /* child */  
  6.     ...  
  7.     sem_wait(mutext);  
  8.     ...  
  9. }  
  10. /* parent */  
  11. ...  
  12. sem_post(mutex);  
  13. ...  



3.1 常用函数说明
sem_t *sem_open(const char *name, int oflag,
                mode_t mode, unsigned int value);
    返回一个sem_t类型的指针。该指针随后可用作sem_close等的参数。
    该函数参数的详细信息,可以参考手册。

int sem_close(sem_t *sem);
    关闭sem信号量,并释放资源。   
int sem_unlink(const char *name);
    在所有进程关闭信号量后删除name的信号量

3.2 有名信号量的使用

例子:

  1. /* 
  2.  * chainname.c 
  3.  */     
  4. #include "my_unpipc.h"  
  5.   
  6. #define BUFSIZE 1024  
  7. #define PERMS (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)  
  8. #define FLAGS (O_CREAT | O_EXCL)  
  9.   
  10. static int getnamed(char *name, sem_t **sem, int val);  
  11.   
  12. int main  (int argc, char *argv[]) {  
  13.    char buffer[BUFSIZE];  
  14.    char *c;  
  15.    pid_t childpid = 0;  
  16.    int delay;  
  17.    volatile int dummy = 0;  
  18.    int i, n;  
  19.    sem_t *semlockp;  
  20.   
  21.    if (argc != 4){       /* check for valid number of command-line arguments */  
  22.       fprintf (stderr, "Usage: %s processes delay semaphorename\n", argv[0]);  
  23.       return 1;  
  24.    }  
  25.    n = atoi(argv[1]);  
  26.    delay = atoi(argv[2]);  
  27.    for (i = 1; i < n; i++)  
  28.       if ((childpid = fork()) > 0)    /* father break */  
  29.          break;  
  30.    snprintf(buffer, BUFSIZE,  
  31.       "i:%d  process ID:%ld  parent ID:%ld  child ID:%ld\n",  
  32.        i, (long)getpid(), (long)getppid(), (long)childpid);  
  33.    c = buffer;  
  34.    if (getnamed(argv[3], &semlockp, 1) == -1) {  
  35.       perror("Failed to create named semaphore");  
  36.       return 1;  
  37.    }  
  38.    while (sem_wait(semlockp) == -1)                         /* entry section */  
  39.        if (errno != EINTR) {  
  40.           perror("Failed to lock semlock");  
  41.           return 1;  
  42.        }  
  43.    while (*c != '\0') {                                  /* critical section */  
  44.       fputc(*c, stderr);  
  45.       c++;  
  46.       for (i = 0; i < delay; i++)  
  47.          dummy++;  
  48.    }  
  49.    if (sem_post(semlockp) == -1) {                           /* exit section */  
  50.       perror("Failed to unlock semlock");  
  51.       return 1;  
  52.    }  
  53.    if (wait(NULL) == -1)                              /* remainder section */  
  54.       return 1;  
  55.    return 0;  
  56. }  
  57.   
  58. static int getnamed(char *name, sem_t **sem, int val)  
  59. {  
  60.    while (((*sem = sem_open(name, FLAGS , PERMS, val)) == SEM_FAILED) &&  
  61.            (errno == EINTR)) ;  
  62.    if (*sem != SEM_FAILED)  
  63.        return 0;  
  64.    if (errno != EEXIST)  
  65.       return -1;  
  66.    while (((*sem = sem_open(name, 0)) == SEM_FAILED) && (errno == EINTR)) ;  
  67.    if (*sem != SEM_FAILED)  
  68.        return 0;  
  69.    return -1;  
  70. }  



以上代码创建了一个进程链,若把sem_wait和sem_post调用去掉,可以看到输出很混乱。
这是由于每个子进程都共享了父进程的文件表项,而且都指向打开的文件表项。

system v 信号量
===============
1, 该类信号量,与posix信号量不同。它表示的信号量集,而不是单个信号量。
可用于不同进程间的同步。
内核为每个信号量集,维护一个如下的信息结构:
  1. struct semid_ds {  
  2.     struct ipc_perm sem_perm;    /* 信号量集的操作许可权限 */  
  3.     struct sem *sem_base;        /* 某个信号量sem结构数组的指针, 
  4.                                    当前信号量集中的每个信号量对应其中一个数组元素 */  
  5.     ushort sem_nsems;            /* sem_base 数组的个数 */  
  6.     time_t sem_otime;            /* 最后一次成功修改信号量数组的时间 */  
  7.     time_t sem_ctime;            /* 成功创建时间 */  
  8. };  
  9.   
  10. struct sem {  
  11.     ushort semval;        /* 信号量的当前值 */  
  12.     short  sempid;        /* 最后一次返回该信号量的进程ID号 */  
  13.     ushort semncnt;        /* 等待semval大于当前值的进程个数 */  
  14.     ushort semzcnt;        /* 等待semval变成0的进程个数 */  
  15. };  



2, 信号量操作函数
a. 创建和打开信号量
int semget(key_t key, int nsems, int oflag)
(1) nsems>0  : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不能更改。
(2) nsems==0 : 访问一个已存在的集合
(3) 返回的是一个称为信号量标识符的整数,semop和semctl函数将使用它。
(4) 创建成功后一下结构被设置:
    .sem_perm 的uid和gid成员被设置成的调用进程的有效用户ID和有效组ID
    .oflag 参数中的读写权限位存入sem_perm.mode
    .sem_otime 被置为0,sem_ctime被设置为当前时间
    .sem_nsems 被置为nsems参数的值
    .而于该集合中的每个信号量不初始化,这些结构是在semctl,用参数SET_VAL,SETALL初始化的。

b. 设置信号量的值
int semop(int semid, struct sembuf *opsptr, size_t nops);
(1) semid 是semget返回的semid
(2) nops : 是数组opsptr的个数
(3) opsptr : 是操作结构的数组

  1. struct sembuf {  
  2.     short sem_num;    /* 信号量的数目: 0,1,...,nsems-1 */  
  3.     short sem_op;    /* 信号量操作 */  
  4.     short sem_flg;  /* 操作表示符 */  
  5. };  


(4) 若sem_op 是正数,其值就加到semval上;
    若sem_op 是0,那么调用者希望等到semval变为0,如果semval是0就反回;
    若sem_op 是负数,那么调用者希望等待semval变为大于或等于sem_op的绝对值.
(5) sem_flg
    SEM_UNDO     由进程自动释放信号量
    IPC_NOWAIT  不阻塞

c. 对信号量集实行控制操作
int semctl(int semid, int semnum, int cmd, ../* union semun arg */);
其中semid是信号量集合,semnum是信号在集合中的序号,

  1. union semun  
  2. {  
  3.     int val; /* cmd == SETVAL */  
  4.     struct semid_ds *buf /* cmd == IPC_SET或者 cmd == IPC_STAT */  
  5.     ushort *array; /* cmd == SETALL, 或 cmd = GETALL */  
  6. };  



cmd是控制命令,参数可选
cmd取值如下:
GETVAL, SETVAL : semid集合中semnum信号量当前的semval值
GETALL,SETALL :semid集合中所有信号量的值。
IPC_RMID:删除semid信号量集
GETPID:返回最后成功操作该信号的进程号。
IPC_STAT:返回semid集合中的struct semid_ds结构。

例子:

  1. /* my_sem.c */  
  2.   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10.    
  11. int main (int argc, char **argv)  
  12. {  
  13.     key_t ipckey;  
  14.     int semid;  
  15.     /*建立两个信号灯结构*/  
  16.     struct sembuf sem[2]; /* sembuf defined in sys/sem.h */  
  17.     /* 创建IPC Key */  
  18.     ipckey = ftok("/tmp/rich", 42);  
  19.     /* 创建信号量. 4 == READ, 2 == ALTER */  
  20.     semid = semget(ipckey, 1, 0666 | IPC_CREAT);  
  21.     if (semid < 0)    
  22.     {  
  23.         printf("Error - %sn", strerror(errno));  
  24.         _exit(1);  
  25.     }  
  26.     /*设置*/  
  27.     /* These never change so leave them outside the loop */  
  28.     sem[0].sem_num = 0;  
  29.     sem[1].sem_num = 0;  
  30.     sem[0].sem_flg = SEM_UNDO; /* Release semaphore on exit */  
  31.     sem[1].sem_flg = SEM_UNDO; /* Release semaphore on exit */  
  32.     while(1)  
  33.     {    
  34.         printf("[%s] Waiting for the semaphore to be releasedn\n", argv[1]);  
  35.         /* 设置两个信号灯,灯1等待,灯2请求资源锁 */  
  36.         sem[0].sem_op = 0; /* Wait for zero */  
  37.         sem[1].sem_op = 1; /* Add 1 to lock it*/  
  38.         /*设置信号量集,两个信号量*/  
  39.         semop(semid, sem, 2);  
  40.    
  41.         /*资源锁区*/  
  42.         printf("[%s] I have the semaphoren\n", argv[1]);  
  43.         sleep(rand() % 3);    
  44.         /* Critical section, sleep for 0-2 seconds */  
  45.         sem[0].sem_op = -1; /* Decrement to unlock */  
  46.         /*出锁,对信号量1操作*/  
  47.         semop(semid, sem, 1);  
  48.         printf("[%s] Released semaphoren\n", argv[1]);  
  49.         sleep(rand() % 3); /* Sleep 0-2 seconds */  
  50.     }  
  51. }   
阅读(349) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~