信号量分有名和无名信号量。它们的区别和管道及命名管道的区别类似。有名信号量要求创建一个文件,而无名信号量则直接保存在内存中。
一,Posix信号量
Posix信号量接口总结(见下图):
上面一行是有名信号量,可于fifo相类比,其值保存在文件中,可用于进程和线程同步;
下面一行是无名信号量,可与pipe相类比,其值保存在内存中,可用于进程和线程同步;
中间部分,是两者的公用接口。
- sem_open() sem_close(),sem_unlink() //有名信号量
- \ |sem_wait(),sem_post() |/
- / |sem_trywait(),sem_getvalue()|\sem_destroy() //无名信号量
- sem_init()
1.公共接口
1.1 接口函数说明
#include <semaphore.h>
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:
多线程使用信号量的简单例子:
- /*
- * simple_sem_app.c
- */
- #include "all.h"
- /* 每个字符输出的间隔时间 */
- #define TEN_MILLION 5000000L
- #define BUFSIZE 1024
- void *threadout(void *args);
- int main(int argc, char *argv[])
- {
- int error;
- int i;
- int n;
- sem_t semlock;
- pthread_t *tids;
- if (argc != 2) {
- fprintf (stderr, "Usage: %s numthreads\n", argv[0]);
- return 1;
- }
- n = atoi(argv[1]);
- tids = (pthread_t *)calloc(n, sizeof(pthread_t));
- if (tids == NULL) {
- perror("Failed to allocate memory for thread IDs");
- return 1;
- }
- if (sem_init(&semlock, 0, 1) == -1) {
- perror("Failed to initialize semaphore");
- return 1;
- }
- for (i = 0; i < n; i++) {
- if (error = pthread_create(tids + i, NULL, threadout, &semlock)) {
- fprintf(stderr, "Failed to create thread:%s\n", strerror(error));
- return 1;
- }
- }
- for (i = 0; i < n; i++) {
- if (error = pthread_join(tids[i], NULL)) {
- fprintf(stderr, "Failed to join thread:%s\n", strerror(error));
- return 1;
- }
- }
- return 0;
- }
- void *threadout(void *args)
- {
- char buffer[BUFSIZE];
- char *c;
- sem_t *semlockp;
- struct timespec sleeptime;
- semlockp = (sem_t *)args;
- sleeptime.tv_sec = 0;
- sleeptime.tv_nsec = TEN_MILLION;
- snprintf(buffer, BUFSIZE, "This is thread from process %ld\n",
- (long)getpid());
- c = buffer;
- /****************** entry section *******************************/
- while (sem_wait(semlockp) == -1)
- if(errno != EINTR) {
- fprintf(stderr, "Thread failed to lock semaphore\n");
- return NULL;
- }
- /****************** start of critical section *******************/
- while (*c != '\0') {
- fputc(*c, stderr);
- c++;
- nanosleep(&sleeptime, NULL);
- }
- /****************** exit section ********************************/
- if (sem_post(semlockp) == -1)
- fprintf(stderr, "Thread failed to unlock semaphore\n");
- /****************** remainder section ***************************/
- return NULL;
- }
说明:该例子来自于usp。
可以把sem_wait和sme_post调用去掉,看看效果,可以看到出现了交叉输出的情况。
nanosleep调用只是为了让输出的效果更明显,没有其他意义。
更多的例子见mypxsem/prodcons2-4.c
3. 有名信号量
有名信号量是把信号量的值保存在文件中,所以它可以用于线程也可以用于进程间的同步。
如下面的形式:
- sem_t *mutex;
- ...
- mutex = sem_open(pathname, O_CREAT | O_EXCL, FILE_MODE, 0);