上一章,述说了SYSTEMV讯号量,主要运行于进程之间,本章主要介绍POSIX讯号量:有名讯号量、无名讯号量。
POSIX讯号量
POSIX讯号量进程是3种IPC(Inter-ProcessCommunication)机制之一,3种IPC机制始于POSIX.1的实时扩充。SingleUNIXSpecification将3种机制(消息队列,讯号量和共享储存)放在可选部份中。在SUSv4之前,POSIX讯号量插口早已被包含在讯号量选项中。在SUSv4中,这种插口被移至了基本规范,而消息队列和共享储存插口仍然是可选的。
POSIX讯号量插口意在解决XSI讯号量插口的几个缺陷。
相比于XSI插口,POSIX讯号量插口考虑了更高性能的实现。
POSIX讯号量使用更简单:没有讯号量集,在熟悉的文件系统操作后一些插口被模式化了。虽然没有要求一定要在文件系统中实现,而且一些系统的确是如此实现的。
POSIX讯号量在删掉时表现更完美。追忆一下,当一个XSI讯号量被删掉时,使用这个讯号量标示符的操作会失败linux下socket编程,并将errno设置成EIDRM。使用POSIX讯号量时,操作能继续正常工作直至该讯号量的最后一次引用被释放。
分类
POSIX讯号量是一个sem_t类型的变量linux fork函数信号量,但POSIX有两种讯号量的实现机制:无名讯号量和命名讯号量。无名讯号量只可以在共享显存的情况下,例如实现进程中各个线程之间的互斥和同步,因而无名讯号量也被叫做基于显存的讯号量;命名讯号量一般用于不共享显存的情况下,例如进程间通讯。
同时,在创建讯号量时,按照讯号量取值的不同,POSIX讯号量还可以分为:
区别
有名讯号量和无名讯号量的差别在于创建和销毁的方式上,然而其他工作一样。
无名讯号量只能存在于显存中,要求使用讯号量的进程必须能访问讯号量所在的这一块显存,所以无名讯号量只能应用在同一进程内的线程之间(共享进程的显存),或则不同进程中早已映射相同显存内容到它们的地址空间中的线程(即讯号量所在显存被通讯的进程共享)。意思是说无名讯号量只能通过共享显存访问。
相反,有名讯号量可以通过名子访问,因而可以被任何晓得它们名子的进程中的线程使用。
单个进程中使用POSIX讯号量时,无名讯号量更简单。多个进程间使用POSIX讯号量时,有名讯号量更简单。
联系
无论是有名讯号量还是无名讯号量,都可以通过以下函数进行讯号量值操作。
wait(P)
wait为讯号量值减一操作,总共有三个函数,函数原型如下:
#include
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
Link with -pthread.这一句表示 gcc 编译时,要加 -pthread.
返回值:
若成功,返回 0 ;若出错,返回-1
结构体定义如下:
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
倘若指定的阻塞时间到了,而且sem依然大于0,则会返回一个错误(错误设置为ETIMEDOUT)。
post(V)
post为讯号量值加一操作,函数原型如下:
#include
int sem_post(sem_t *sem);
Link with -pthread.
返回值:
若成功,返回 0 ;若出错,返回-1
无名讯号量插口函数
讯号量的函数都以sem_开头,线程中使用的基本讯号函数有4个,她们都申明在头文件semaphore.h中,该头文件定义了用于讯号量操作的sem_t类型:
sem_init
该函数用于创建讯号量,原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:该函数初始化由sem指向的讯号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制讯号量的类型,假如其值为0,就表示讯号量是当前进程的局部讯号量,否则讯号量就可以在多个进程间共享,value为sem的初始值。返回值:该函数调用成功返回0,失败返回-1。
sem_destroy
该函数用于对用完的讯号量进行清除,其原型如下:
int sem_destroy(sem_t *sem);
返回值:
成功返回0,失败返回-1。
sem_getvalue函数
该函数返回当前讯号量的值,通过restrict输出参数返回。假如当前讯号量早已上锁(即同步对象不可用),这么返回值为0,或为正数,其绝对值就是等待该讯号量解锁的线程数。
int sem_getvalue(sem_t *restrict, int *restrict);
使用实例
【实例1】:
#include
#include
#include
#include
#include
#include
#include
#include
sem_t sem;
#define handle_error(msg) do {
perror(msg);
exit(EXIT_FAILURE);
}while (0)
static void handler(int sig){
write(STDOUT_FILENO, "sem_post() from handlern", 24);
if(sem_post(&sem) == -1)
{
write(STDERR_FILENO, "sem_post() failedn", 18);
_exit(EXIT_FAILURE);
}}
int main(int argc, char *argv[]){
int s;
struct timespec ts;
struct sigaction sa;
if (argc != 3)
{
fprintf(stderr, "Usage: %s n", argv[0]);
exit(EXIT_FAILURE);
}
if (sem_init(&sem, 0, 0) == -1)
handle_error("sem_init");
/* Establish SIGALRM handler; set alarm timer using argv[1] */
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGALRM, &sa, NULL) == -1)
handle_error("sigaction");
alarm(atoi(argv[1]));
/* Calculate relative interval as current time plus
number of seconds given argv[2] */
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
handle_error("clock_gettime");
ts.tv_sec += atoi(argv[2]);
printf("main() about to call sem_timedwait()n");
while ((s = sem_timedwait(&sem, &ts)) == -1 && errno == EINTR)
continue; /* Restart if interrupted by handler */
/* Check what happened */
if (s == -1)
{
if (errno == ETIMEDOUT)
printf("sem_timedwait() timed outn");
else
perror("sem_timedwait");
}
else
{
printf("sem_timedwait() succeededn");
}
exit((s == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
【实例2】:
#include
#include
#include
#include
#include
#include
#include
#include
sem_t sem;
void *func1(void *arg){
sem_wait(&sem);
int *running = (int *)arg;
printf("thread func1 running : %dn", *running);
pthread_exit(NULL);
}
void *func2(void *arg)
{
printf("thread func2 running.n");
sem_post(&sem);
pthread_exit(NULL);
}
int main(void)
{
int a = 3;
sem_init(&sem, 0, 0);
pthread_t thread_id[2];
pthread_create(&thread_id[0], NULL, func1, (void *)&a);
printf("main thread running.n");
sleep(10);
pthread_create(&thread_id[1], NULL, func2, (void *)&a);
printf("main thread still running.n");
pthread_join(thread_id[0], NULL);
pthread_join(thread_id[1], NULL);
sem_destroy(&sem);
return 0;
}
有名讯号量
有时侯也叫命名讯号量,之所以称为命名讯号量,是由于它有一个名子、一个用户ID、一个组ID和权限。那些是提供给不共享显存的这些进程使用命名讯号量的插口。命名讯号量的名子是一个遵循路径名构造规则的字符串。
插口函数
sem_open函数
该函数用于创建或打开一个命名讯号量,其原型如下:
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_close函数
该函数用于关掉命名讯号量:
int sem_close(sem_t *);
功能:单个程序可以用sem_close函数关掉命名讯号量,然而这样做并不能将讯号量从系统中删掉,由于命名讯号量在单个程序执行之外是具有持久性的。当进程调用_exit、exit、exec或从main返回时linux命令tar,进程打开的命名讯号量同样会被关掉。
sem_unlink函数功能:sem_unlink函数用于在所有进程关掉了命名讯号量以后,将讯号量从系统中删掉:
int sem_unlink(const char *name);
讯号量操作函数与无名讯号量一样。
使用实例
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SEM_NAME " /sem_name"
sem_t *p_sem;
void *testThread(void *ptr){
sem_wait(p_sem);
sleep(2);
pthread_exit(NULL);}
int main(void){
int i = 0;
pthread_t pid;
int sem_val = 0;
p_sem = sem_open(SEM_NAME, O_CREAT, 0555, 5);
if(p_sem == NULL)
{
printf("sem_open %s failed!n", SEM_NAME);
sem_unlink(SEM_NAME);
return -1;
}
for(i = 0; i < 7; i++)
{
pthread_create(&pid, NULL, testThread, NULL);
sleep(1);
// pthread_join(pid, NULL); // not needed, or loop
sem_getvalue(p_sem, &sem_val);
printf("semaphore value : %dn", sem_val);
}
sem_close(p_sem);
sem_unlink(SEM_NAME);
return 0;
}
命名和无名讯号量的持续性
命名讯号量是随内核持续的。当命名讯号量创建后,虽然当前没有进程打开某个讯号量,它的值仍然保持,直至内核重新自举或调用sem_unlink()删掉该讯号量。
无名讯号量的持续性要按照讯号量在显存中的位置确定:
倘若无名讯号量是在单个进程内部的数据空间中,即讯号量只能在进程内部的各个线程间共享,这么讯号量是随进程的持续性,当进程中止时他也就消失了;
倘若无名讯号量坐落不同进程的共享显存区,因而只要该共享显存区一直存在linux fork函数信号量,该讯号量都会仍然存在;所以此时无名讯号量是随内核的持续性。
讯号量-互斥量-条件变量
好多时侯讯号量、互斥量和条件变量都可以在某种应用中使用,那这两者的差别有什么呢?下边列举了这两者之间的差别: