POSIX信号量有两种:有名信号量和无名信号量,无名信号量也被称作基于内存的信号量。有名信号量通过IPC名字进行进程间的同步,而无名信号量如果不是放在进程间的共享内存区中,是不能用来进行进程间同步的,只能用来进行线程同步
System V的信号量一般用于进程同步, 且是内核持续的, api为
semget
semctl
semop
Posix的有名信号量一般用于进程同步, 有名信号量是内核持续的. 有名信号量的api为
sem_open
sem_close
sem_unlink
Posix的无名信号量一般用于线程同步, 无名信号量是进程持续的, 无名信号量的api为
sem_init
sem_destroy
1.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_CREAT,O_EXCL。如果为0表示打开一个已存在的信号量,如果为O_CREAT,表示如果信号量不存在就创建一个信号量,如果存在则打开被返回。此时mode和value需要指定。如果为O_CREAT | O_EXCL,表示如果信号量已存在会返回错误。
mode参数用于创建信号量时,表示信号量的权限位,和open函数一样包括:S_IRUSR,S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH,S_IWOTH。
value表示创建信号量时,信号量的初始值。
#include <semaphore.h>
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
//成功返回0,失败返回-1
sem_close用于关闭打开的信号量。当一个进程终止时,内核对其上仍然打开的所有有名信号量自动执行这个操作。调用sem_close关闭信号量并没有把它从系统中删除它,POSIX有名信号量是随内核持续的。即使当前没有进程打开某个信号量它的值依然保持。直到内核重新自举或调用sem_unlink()删除该信号量。
sem_unlink用于将有名信号量立刻从系统中删除,但信号量的销毁是在所有进程都关闭信号量的时候。
2.1. 信号量的P操作
#include <semaphore.h>
int sem_wait (sem_t *sem);
#ifdef __USE_XOPEN2K
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
#endif
int sem_trywait (sem_t * sem);
//成功返回0,失败返回-1
sem_wait()用于获取信号量,首先会测试指定信号量的值,如果大于0,就会将它减1并立即返回,如果等于0,那么调用线程会进入睡眠,指定信号量的值大于0.
sem_trywait和sem_wait的差别是,当信号量的值等于0的,调用线程不会阻塞,直接返回,并标识EAGAIN错误。
sem_timedwait和sem_wait的差别是当信号量的值等于0时,调用线程会限时等待。当等待时间到后,信号量的值还是0,那么就会返回错误。其中 struct timespec *abs_timeout是一个绝对时间,具体可以参考条件变量关于等待时间的使用
3.1. 信号量的V操作
#include <semaphore.h>
int sem_post(sem_t *sem);
//成功返回0,失败返回-1
当一个线程使用完某个信号量后,调用sem_post,使该信号量的值加1,如果有等待的线程,那么会唤醒等待的一个线程。
4.1. 获取当前信号量的值
#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
//成功返回0,失败返回-1
该函数返回当前信号量的值,通过sval输出参数返回,如果当前信号量已经上锁(即同步对象不可用),那么返回值为0,或为负数,其绝对值就是等待该信号量解锁的线程数。
5.1:无名信号量的创建和销毁
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
//若出错则返回-1
int sem_destroy(sem_t *sem);
//成功返回0,失败返回-1
sem_init()用于无名信号量的初始化。无名信号量在初始化前一定要在内存中分配一个sem_t信号量类型的对象,这就是无名信号量又称为基于内存的信号量的原因。
sem_init()第一个参数是指向一个已经分配的sem_t变量。第二个参数pshared表示该信号量是否由于进程间通步,当pshared = 0,那么表示该信号量只能用于进程内部的线程间的同步。当pshared != 0,表示该信号量存放在共享内存区中,使使用它的进程能够访问该共享内存区进行进程同步。第三个参数value表示信号量的初始值。
这里需要注意的是,无名信号量不使用任何类似O_CREAT的标志,这表示sem_init()总是会初始化信号量的值,所以对于特定的一个信号量,我们必须保证只调用sem_init()进行初始化一次,对于一个已初始化过的信号量调用sem_init()的行为是未定义的。如果信号量还没有被某个线程调用还好,否则基本上会出现问题。
使用完一个无名信号量后,调用sem_destroy摧毁它。这里要注意的是:摧毁一个有线程阻塞在其上的信号量的行为是未定义的。
有名和无名信号量的持续性
有名信号量是随内核持续的。当有名信号量创建后,即使当前没有进程打开某个信号量它的值依然保持。直到内核重新自举或调用sem_unlink()删除该信号量。
无名信号量的持续性要根据信号量在内存中的位置:
如果无名信号量是在单个进程内部的数据空间中,即信号量只能在进程内部的各个线程间共享,那么信号量是随进程的持续性,当进程终止时它也就消失了。
(进程内部各个线程共享,除了用无名信号外,还可以用条件变量,可以参考:
https://blog.csdn.net/ding283595861/article/details/103385069)如果无名信号量位于不同进程的共享内存区,因此只要该共享内存区仍然存在,该信号量就会一直存在。所以此时无名信号量是随内核的持续性。
对于有名信号量在父进程中打开的任何有名信号量在子进程中仍是打开的 对于无名信号量的继承要根据信号量在内存中的位置:
1):如果无名信号量是在单个进程内部的数据空间中,那么信号量就是进程数据段或者是堆栈上,当fork产生子进程后,该信号量只是原来的一个拷贝,和之前的信号量是独立的。
2):如果无名信号量位于不同进程的共享内存区,那么fork产生的子进程中的信号量仍然会存在该共享内存区,所以该信号量仍然保持着之前的状态。
在 POSIX 标准中,信号量分两种,一种是无名信号量,一种是有名信号量。无名信号量一般用于线程间同步或互斥,而有名信号量一般用于进程间同步或互斥。它们的区别和管道及命名管道的区别类似,无名信号量则直接保存在内存中,而有名信号量要求创建一个文件
#include <iostream>
#include<stdio.h>
#include<string.h>
#include <unistd.h>
#include <semaphore.h>
#include <fcntl.h>
using namespace std;
#define SEM_NAME "/sem_name"
sem_t *pSem;
void * testThread (void *ptr)
{
sem_wait(pSem);
printf("###read or write the data area###\n");
sleep(20);
sem_close(pSem);
}
int main()
{
pSem = sem_open(SEM_NAME, O_CREAT, 0666, 5);
pthread_t pid;
int semVal;
for (int i = 0; i < 7; ++i)
{
pthread_create(&pid, NULL, testThread, NULL);
sleep(1);
sem_getvalue(pSem, &semVal);
cout<<"semaphore value:"<<semVal<<endl;
}
sem_close(pSem);
sem_unlink(SEM_NAME);
}
g++ -o sem_test sem_test.c -pthread
执行结果
semcreate.c
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char** argv){
int c, flags;
unsigned int val = 1;
sem_t* sem;
flags = O_RDWR | O_CREAT;
while((c = getopt(argc, argv, "ei:")) != -1){
switch(c){
case 'e':
flags |= O_EXCL;
break;
case 'i':
val = atoi(optarg);
break;
}
}
if(optind != argc - 1){
printf("usage error\n");
return -1;
}
sem = sem_open(argv[optind], flags, 0664, val);
if(sem == SEM_FAILED){
perror("sem");
return -1;
}
sem_close(sem);
exit(0);
}
创建有名信号量,发现在/dev/shm/下生成了sem.semtest,mode是0664,但是umask是0022,所以sem.semtest mode是0644
semunlink.c
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char** argv){
if(argc != 2){
printf("usage err\n");
exit(1);
}
if(sem_unlink(argv[1]) == -1){
perror("sem_unlink");
}
}
semgetvalue.c
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char** argv){
sem_t* sem;
int val;
if(argc != 2){
printf("usage error\n");
exit(1);
}
sem = sem_open(argv[1], 0);
sem_getvalue(sem, &val);
printf("value = %d\n", val);
exit(0);
}
查看信号量的value值
semwait.c
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char** argv){
sem_t* sem;
int val;
if(argc != 2){
printf("usage error\n");
exit(1);
}
sem = sem_open(argv[1], 0);
sem_wait(sem);
sem_getvalue(sem, &val);
printf("pid %ld has semaphore, value = %d\n", (long) getpid(), val);
pause();
exit(0);
}
等待信号量,发现执行完sem_wait函数后,信号量的value值变更为0了。
sempost.c
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char** argv){
sem_t* sem;
int val;
if(argc != 2){
printf("usage error\n");
exit(1);
}
sem = sem_open(argv[1], 0);
sem_post(sem);
sem_getvalue(sem, &val);
printf("value = %d\n", val);
exit(0);
}
等待信号量2次,由于执行前value值已经是0了,按理来说执行了2次sem_wait后,应该变成-2,但是发现还行0。不要惊慌,内核是记住了value的值为-2的。
连续执行sempost 四次,可以看到,只有后两次value才执行累加, 从这里可以看出,当连续执行wait操作的时候,通过getvalue获取的value值只能>=0 , 但是内核会记住调用wait的次数,可以通过相应的post来恢复其真正的值.