system V信号量和Posix信号量
一、函数上的区别
信号量有两种实现:传统的System V信号量和新的POSIX信号量。它们所提供的函数很容易被区分:对于所有System V信号量函数,在它们的名字里面没有下划线。例如,应该是semget()而不是sem_get()。然而,所有的的POSIX信号量函数都有一个下划线。下面列出了它们提供的所有函数清单:
Systm V | POSIX |
semctl() | sem_getvalue() |
semget() | sem_post() |
semop() | sem_timedwait() |
sem_trywait() | |
sem_wait() | |
sem_destroy() | |
sem_init() | |
sem_close() | |
sem_open() | |
sem_unlink() |
二、使用上的区别
1、XSI system V的信号量是信号量集,可以包括多个信号灯(有个数组),每个操作可以同时操作多个信号灯
posix是单个信号灯,POSIX有名信号灯支持进程间通信,无名信号灯放在共享内存中时可以用于进程间通信。
2、POSIX信号量在有些平台并没有被实现,比如:SUSE8,而SYSTEM V大多数LINUX/UNIX都已经实现。两者都可以用于进程和线程间通信。但一般来说,system v信号量用于 进程间同步、有名信号灯既可用于线程间的同步,又可以用于进程间的同步、posix无名用于同一个进程的不同线程间,如果无名信号量要用于进程间同步,信号量要放在共享内存中。
3、POSIX有两种类型的信号量,有名信号量和无名信号量。有名信号量像system v信号量一样由一个名字标识。
4、POSIX通过sem_open单一的调用就完成了信号量的创建、初始化和权限的设置,而system v要两步。也就是说posix 信号是多线程,多进程安全的,而system v不是,可能会出现问题。
5、system V信号量通过一个int类型的值来标识自己(类似于调用open()返回的fd),而sem_open函数返回sem_t类型(长整形)作为posix信号量的标识值。
6、对于System V信号量你可以控制每次自增或是自减的信号量计数,而在Posix里面,信号量计数每次只能自增或是自减1。
7、Posix无名信号量提供一种非常驻的信号量机制。
8、相关进程: 如果进程是从一已经存在的进程创建,并最终操作这个创建进程的资源,那么这些进程被称为相关的。
三、注意事项
1、Posix有名信号灯的值是随内核持续的。也就是说,一个进程创建了一个信号灯,这个进程结束后,这个信号灯还存在,并且信号灯的值也不会改变。当持有某个信号灯锁的进程没有释放它就终止时,内核并不给该信号灯解锁
2、posix有名信号灯是通过内核持续的,一个进程创建一个信号灯,另外的进程可以通过该信号灯的外部名(创建信号灯使用的文件名)来访问它。posix基于内存的无名信号灯的持续性却是不定的,如果基于内存的信号灯是由单个进程内的各个线程共享的,那么该信号灯就是随进程持续的,当该进程终止时它也会消失。如果某个基于内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,这要只要该共享内存区存在,该信号灯就存在。
四、总结
1、System V的信号量一般用于进程同步, 且是内核持续的, api为:semget、semctl、semop
2、Posix的有名信号量一般用于进程同步, 有名信号量是内核持续的. 有名信号量的api为:sem_open、sem_close、sem_unlink
3、Posix的无名信号量一般用于线程同步, 无名信号量是进程持续的, 无名信号量的api为:sem_init、sem_destroy
五、代码示例
<span style="background-color: rgb(255, 255, 255);">/*</span><span style="background-color: rgb(255, 255, 252);"> * MultiProcessLock.cpp * * Created on: 2013-8-28 * Detail: System V 信号量的使用 */ #include <sys/sem.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <unistd.h> #include <errno.h> // val当执行SETVAL命令时使用。buf在IPC_STAT/IPC_SET命令中使用。代表了内核中使用的信号量的数据结构。array在使用GETALL/SETALL命令时使用的指针。 union semun { int val; /*value for SETVAL*/ struct semid_ds *buf; /*buffer for IPC_STAT & IPC_SET*/ unsigned short int *array; /*array for GETALL & SETALL*/ struct seminfo *__buf; /*buffer for IPC_INFO*/ }; /* 等待一个二元信号量:阻塞直到信号量的值为正,然后将其减1 */ int binary_semaphore_wait(int semid) { struct sembuf sem_b; sem_b.sem_num = 0; /* 减一。 */ sem_b.sem_op = -1; /* 允许撤销操作 */ sem_b.sem_flg = SEM_UNDO; return semop (semid, &sem_b, 1); } /* 对一个二元信号量执行投递操作:将其值加一。 这个操作会立即返回 */ int binary_semaphore_post (int semid) { struct sembuf sem_b; /* 使用(且仅使用)第一个信号量 */ sem_b.sem_num = 0; /* 加一 */ sem_b.sem_op = 1; /* 允许撤销操作 */ sem_b.sem_flg = SEM_UNDO; return semop (semid, &sem_b, 1); } int main() { int iSemId; int nsems = 1; /* * 通过 IPC_CREAT | IPC_EXCL 可以判断这个key对应的信号量是不是已经存在了,这可以用在单进程运行多次的情况下,只初始化一次信号量的情况下 */ int semflg = 0666 | IPC_CREAT | IPC_EXCL; // 如果key对应的((semflg &IPC_CREAT) &&(semflg &IPC_EXCL))非0, 那么semget返回EEXIST key_t key = (key_t)0x20130828; /*创建一个新的信号量集*/ iSemId = semget(key, nsems, semflg); if (iSemId < 0 && errno != EEXIST) { perror( "semget ") ; return -1; } if(iSemId >= 0) // 这个信号量是第一次创建,则初始化 { printf("create: semid is %d\n", iSemId); /* 初始化信号量 */ semun sem_union; sem_union.val = 1; if(semctl(iSemId, 0, SETVAL, sem_union) == -1) { perror("semctl"); return -1; } } else // 这个信号量已经有了,则获得这个信号量值 { iSemId = semget(key, nsems, 0666); printf("exit: semid is %d\n", iSemId); } /* 进程同步执行 */ for(int i = 0; i < 10; ++i) { if(binary_semaphore_wait(iSemId)) { perror("semop: "); exit(-1); } printf("%d ", getpid()); sleep(1); printf("%d\n", getpid()); if(binary_semaphore_post(iSemId)) { exit(-1); } } return 0; }</span>
/* posixSemProgessLock.cpp * * Created on: 2013-8-29 * Detail: 使用Posix信号量的进程同步 */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> #include <fcntl.h> #define SEM_NAME "0x20130829" int main() { /* 初始化一个有名二元信号灯 */ sem_t *sem; sem = sem_open(SEM_NAME, O_CREAT, 0666, 10); if(sem == SEM_FAILED) { perror("sem init failed:"); return -1; } int val; sem_wait(sem); sem_getvalue(sem,&val); printf("1: getvalue: value=%d, pid=%d\n",val, getpid()); sleep(1); printf("2: getvalue: value=%d, pid=%d\n",val, getpid()); sem_post(sem); sleep(1); sem_getvalue(sem,&val); printf("3: getvalue: value=%d, pid=%d\n",val, getpid()); sem_unlink(SEM_NAME); return 0; }
PV原子操作主要用于进程或线程间的同步和互斥这两种典型情况。若用于互斥,几个进程(或线程)往往只设置一个信号量sem。 当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现它们之间的顺序执行,下面是一个线程同步的例子:
/*thread_sem.c*/ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h> #define THREAD_NUMBER 3 /* 线程数 */ #define REPEAT_NUMBER 3 /* 每个线程中的小任务数 */ #define DELAY_TIME_LEVELS 10.0 /*小任务之间的最大时间间隔*/ sem_t sem[THREAD_NUMBER]; void *thrd_func(void *arg) { int thrd_num = (int)arg; int delay_time = 0; int count = 0; /* 进行P操作 */ sem_wait(&sem[thrd_num]); printf("Thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread %d: job %d delay = %d\n", thrd_num, count, delay_time); } printf("Thread %d finished\n", thrd_num); pthread_exit(NULL); } int main(void) { pthread_t thread[THREAD_NUMBER]; int no = 0, res; void * thrd_ret; srand(time(NULL)); for (no = 0; no < THREAD_NUMBER; no++) { sem_init(&sem[no], 0, 0); int val; sem_getvalue(&sem[no],&val); printf("val is %d\n", val); res = pthread_create(&thread[no], NULL, thrd_func, (void*)no); if (res != 0) { printf("Create thread %d failed\n", no); exit(res); } } printf("Create treads success\n Waiting for threads to finish...\n"); /* 对最后创建的线程的信号量进行V操作 */ sem_post(&sem[THREAD_NUMBER - 1]); for (no = THREAD_NUMBER - 1; no >= 0; no--) { res = pthread_join(thread[no], &thrd_ret); if (!res) { printf("Thread %d joined\n", no); } else { printf("Thread %d join failed\n", no); } /* 进行V操作 */ sem_post(&sem[(no + THREAD_NUMBER - 1) % THREAD_NUMBER]); } for (no = 0; no < THREAD_NUMBER; no++) { /* 删除信号量 */ sem_destroy(&sem[no]); } return 0; }