Posix信号灯
1、 函数列表(#include <semaphore.h>)
有名信号灯特异函数
Ø sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
此函数创建一个新的信号灯或打开一个已存在的有名信号灯,成功时返回指向信号灯的指针,出错时返回SEM_FAILED。
Oflag可以为0、O_CREATE、O_CREATE|O_EXCL。如果O_CREATE,当信号灯存在时,则不初始化它,当信号灯不存在时,则创建并初始化它。如果O_CREATE|O_EXCL,当信号灯存在时,错误,当信号灯不存在时,则创建并初始化它。
Mode同FIFO的mkfifo函数,指定S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH表示FIFO允许用户读、用户写、组成员读和其他用户读。
Value为信号灯的初始值,二值信号灯的初始值通常为1,计数信号灯的初始值则往往大于1.
Ø int sem_close(sem_t *sem);
此函数用于关闭有名信号灯,成功返回0,失败返回-1。
Ø int sem_unlink(const char *name);
此函数用于删除有名信号灯,成功返回0,失败返回-1.
无名信号灯特异函数
Ø int sem_init(sem_t *sem, int pshared, unsigned int value);
此函数用于初始化基于内存空间的无名信号灯,成功不一定返回0,失败返回-1.
sem参数指向必须由应用程序分配的sem_t变量;
pshared为0时,那么初始化的信号灯是在同一个进程的各个线程间共享的,为1的话就是在进程间共享的,此时该信号灯必须放在即将使用他的所有进程都能访问的某种类型的共享内存区中;
跟sem_open一样,value参数是该信号灯的初始值。
Ø int sem_destroy(sem_t *sem);
此函数用于销毁无名信号灯,成功返回0,失败返回-1.
公共函数
Ø int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
sem_wait测试所指信号灯,如果该值大于0,则减1并立即返回;如果等于0,调用线程进入休眠;
sem_trywait测试所指信号灯,如果该值大于0,则减1并立即返回;如果等于0,此函数返回EAGAIN错误;
sem_timedwait测试所指信号灯,如果该值大于0,则减1并立即返回;如果等于0,此函数阻塞等待abs_timeout时长,超时时,函数返回ETIMEDOUT错误,在abs_timeout内信号灯值变成大于0,则减1并立即返回。
成功时返回0,失败时返回-1.
Ø int sem_post(sem_t *sem);
给该信号灯的值加1,然后唤醒等待该信号灯值变成正数的任意线程,成功时返回0,失败时返回-1。
Ø int sem_getvalue(sem_t *sem, int *sval);
由sval返回所指定信号灯的值,成功返回0,失败返回-1.
2、 实例解析
Ø namedsem.c
Posix有名信号灯在linux中没有实现。
# include <semaphore.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> int main(int argc, char **argv) { int c, flags; sem_t *sem; unsigned int value; flags = O_RDWR | O_CREAT; value = 1; sem = sem_open("/tmp/sem1", flags, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH), value); if(sem==SEM_FAILED) printf("sem=%d/n",sem); else printf("okk!!/n"); sleep(100); sem_close(sem); exit(0); } #gcc namedsem.c –lpthread –o namedsem |
Sem_open返回的始终为SEM_FAILED,并且在目录tmp下始终没有见到sem1文件。
Ø nonamedsem.c
Posix无名信号灯的在单个线程中的基本操作。
# include <semaphore.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> int main(int argc, char **argv) { int a; sem_t *sem; int c=0; c=sem_init(sem,0,11); if(c==-1) printf("sem_init failed/n"); else printf("sem_init success/n"); sem_getvalue(sem,&a); printf("a=%d/n",a); //输出11 sem_post(sem); sem_getvalue(sem,&a); printf("a=%d/n",a); //输出12
sem_wait(sem); sem_getvalue(sem,&a); printf("a=%d/n",a); //输出11 sem_destroy(sem); exit(0); } #gcc nonamedsem.c –lpthread –o nonamedsem |
Ø 互斥与无名信号灯
在线程之间使用一个二值无名信号灯,比较于同步锁。
具体互斥例子见4、互斥和条件变量】【basicmutex】。
区别是互斥锁必须总是由锁住它的线程解锁,信号灯的挂出却不必由执行过它的等待操作的同一线程执行。
Ø 条件变量与无名信号灯
在线程之间使用多个二值无名信号灯,比较于条件变量。
具体条件变量例子见【4、互斥和条件变量】【basiccond】。
条件变量需要绑定的同步锁以及一个特定的变量。而三个信号灯即可实现同样的功能。
3、 小结
(1) 有名信号灯与无名信号灯区别
Ø 有名信号灯由一个name参数标识,它通常指代文件系统中的某个文件;无名信号灯是指基于内存的信号灯,它们由应用程序分配的sem_t数据类型的内存空间,然后由系统初始化他们的值。
Ø Sem_open不需要指定类似于pshared的参数或类似于 PTHREAD_PROCESS_SHARED的属性(互斥锁和条件变量可使用该属性),因为有名信号灯总是可以在不同进程间共享的。
Ø Sem_open函数的作用有两个:创建和初始化有名信号灯;sem_init函数的作用只有一个:初始化无名信号灯,创建由应用程序完成。对于同一个信号灯,如果为有名,则sem_open可以调用多次,而如果为无名,则只能调用一次。
Ø 彼此无亲缘关系的不同进程使用信号灯时,通常使用有名信号灯,其名字就是各个进程标识信号灯的手段;基于内存的信号灯至少具有随进程的持续性,但真正持续性却取决于信号灯存放在其中的内存区的类型,只要含有某个基于内存信号灯的内存区保持有效,该信号灯就一直存在。
(2) 信号灯在线程进程中的使用
Ø 有名信号灯可以在任何线程或进程间使用,因为其实质就是文件系统中的一个文件;
Ø 如果基于内存的信号灯是由单个进程内的各个线程共享的(sem-init的pshared为0),那么该信号灯具有随进程的持续性,当该进程终止时,它就消失。
Ø 如果基于内存的信号灯是在不同进程间共享的(sem_init的pshared为1),那么该信号灯必须存放在共享内存区中,因而只要该共享内存区仍然存在,该信号灯也就继续存在。Posix和system v共享内存区都具有随内核的持续性,这意味着服务器可以创建一个共享内存区,在该共享内存区中初始化一个posix基于内存的信号灯,然后终止,一段时间后,一个或多个客户打开该共享内存区,访问存放在其中的基于内存的信号灯。
(3) 信号灯、互斥与条件变量区别
Ø 互斥锁必须总是由给它上锁的线程解锁,信号灯没有这种限制:一个线程可以等待某个给定信号灯(例如该信号灯值由1变为0,同上锁),而另一个线程可以挂出该信号灯(例如信号灯值由0变为1,同解锁)。
Ø 既然每个信号灯关联一个值,挂出(post)操作加1,等待(wait)操作减1,因此任何线程均可以挂出一个信号,即使当时没有线程在等待该信号灯变正数也没关系。然而,如果某个线程调用了pthread_cond_signal,如果当时没有任何线程阻塞在pthread_cond_wait调用中,那么发往相应条件变量的信号将丢失。
Ø 在各种各样的同步技巧(互斥锁、条件变量、读写锁、信号灯)中,能够从信号处理程序中安全调用的唯一函数是sem_post。
Ø 互斥锁、条件变量、读写锁、信号灯以及记录上锁都有它们各自的位置。互斥锁是为上锁锁优化的,条件变量是为等待而优化的,信号灯既可以用于上锁,也可以用于等待,因而可能导致更多的开销和更高的复杂性。
Ø 使用互斥锁和条件变量实现具有随内核持续性的计数信号灯需要大约300行C代码。信号灯意图进程间通信,互斥锁和条件变量意图线程间通信。
(4) 尽管posix有名信号灯是由可能与文件系统中的路径对应的名字来标识的,但是它们并不要求存放在文件系统内的某个文件中。例如嵌入式实时系统可能使用这样的名字来标识信号灯,但真正的信号等值却存放在内核中的某个地方。
(5) Wait可以:posix术语等待(wait)、P操作、递减(down)和上锁(lock);
Post可以:posix术语挂出(post)、V操作、递增(up)和解锁(unlock)。
(6) 值仅为1或0的信号灯为二值信号灯;值大于1的信号灯为计数信号灯。
(7) 可以使用FIFO、内存映射I/O和System V信号灯实现Posix信号灯。