知乎讨论
通俗的描述信号量,是这么个东西:
每个信号量拥有一个计数器,sem_post()
可以使该计数器+1
(V操作),sem_wait()
在计数器不为0时,可以使该计数器-1
,否则阻塞直到不为0(P操作)。
而该计数器的存在,就可以限定某个容器中元素的个数。
如果单纯的看效果,互斥量+条件变量可以实现计数器为1的信号量。
优缺点
UNP卷二
作者对于信号量的评价为:
- 信号量默认就是进程间共享
对同一个信号量,加锁和解锁可以在不同的进程或是进程进行。
而互斥量只能有是由加锁线程解锁,且默认不再进程间共享。 - 信号量有与之关联的一个值,计数器
该值在信号量结构体内部维护。即使消费者还没有阻塞在sem_wait()
,该值也会递增。
相比与条件变量,如果消费者还没阻塞在pthread_cond_wait()
,那么由pthread_cond_signal()
发送的信号就会丢失,没有函数去处理。 - 相比于其他锁,只有
sem_port()
是异步信号安全函数
异步信号安全函数指的是:可以在信号处理函数中调用的函数。
muduo陈硕
- 信号量不是必备的锁
使用条件标量和互斥量可以完全代替信号量的功能 - 信号量的计数器是个累赘
程序的容器有自己的长度,而信号量又自带一个,这样造成了同样的信息维护两份,需要时刻保持一致。增大了程序员的负担和出错的可能。 - 同时建议使用单独一个线程去分配任务
其他线程使用条件变量阻塞
个人见解
没有项目经验,不保证全对
- 不同线程间可解锁
这还真可能是个累赘。 - 确实可以使用条件变量+互斥量去代替
- 异步信号安全函数
没用过,不知道啊。书上也将,可以在函数内向管道内写东西的方式来激活一个别的线程,解决这些问题。
Posix 信号量
Posix信号量不必在内核中维护。
有名信号量
这个信号量有名字啦,根据这个名字就可以在不同的进程间使用。
这个名字可以时文件系统中对应的名字来表示。
Api
打开/创建有名信号量
#include
sem_t sem;
sem_t *sem_open(const char *pathfile, int flag/, mode_t mode ,unsigned int value/);
打开一个信号量
flag
:可以时0,主要使用来,当指定的文件不存在时,使用O_CREATE | O_EXEL
新建,如果指定为0,后两个参数可省略,否则后面两个参数需要带上。
mode
:文件权限。
value
:信号量初始的值,这这个参数只有在新创建的时候才需要设置,如果是打开已有的,不需要在去指定,否则会报错。该值不超过是SEM_VALUE_MAX
(至少是32767
)。
取消对信号量的使用
#include
int sem_close(sem_t *sem);
//成功返回0,否则返回-1
这个函数只是声明,在这个进程中不再使用这个信号量,并不是去析构该信号量。
同时进程结束的时候,无论是正常还是信号中断退出进程,内核都会主动调用该函数去关闭进程使用的信号量。
即使都没有后进程在使用这个信号量了,内核也会维持这个信号量。
也就是说,Posix的信号量是随着内核持续的。
主动析构信号量
#include
int sem_unlink(const char *name);
//成功返回0,否则-1
对打开的信号量的路径执行,直接删除指定的文件,但是并不析构该信号量。当最后一个使用该信号量的进程调用sem_close()
对该信号量的时候,内核析构该信号量。
P操作
#include
int sme_wait(sem_t *);
int sem_trywait(sem_t *);
//成功返回0 ,否则-1
sem_wait()
测试指定的信号量,如果计数器值大于0,那么递减计数器,并立即返回,否则,阻塞在计数器为0。直到不是0时,递减并返回。如果因为信号中断,则过返回EAGAIN
错误。
sem_trywait()
函数,如果指定信号量的计数器为0,那么直接返回EAGAIN
错误。
为什么阻塞递减叫做P操作?源自荷兰单词proberen
意思是尝试。
V操作
#include
int sme_post(sem_t *);
//成功返回0 ,否则-1
递增指定的信号量计数器+1
同样因为荷兰单词verhogen
增加的意思。
当前计数器的值
#include
int sem_getvalue(sem_t *,int *);
//成功返回0 ,否则-1
传入两个参数,第二个参数是需要填充的。
如果当前信号量已经上锁,也就是有线程在等待,那么该值为0
或者是负数,而如果是负数,那么表示等待该信号量解锁的线程数。
信号量死锁
主要出现在,如果同时使用两个信号量,那么顺序一定不能出错。
基于内存信号量
不使用文件系统标识,直接存在程序运行的内存中,(非共享内存)。
这样就造成了,不同进程之间不能访问,不能用于不同进程之间相互访问。
一个父进程初始化一个信号量,然后fork
其副本得到的是该信号量的副本,这两个信号量之间并不存在关系。
初始化
#include
int sem_init(sem_t *sem,int shared,unsignel int value);
//出错返回-1,成功的不一定。
int sem_destory(sem_t *);
//成功0,失败-1;
第二个参数,表示是否在进程件共享,在这种实现中不可能进程间共享,需要基于共享内存的信号量,才可以。
sem_init()
需要用户自己创建一个结构体(不是指针)。然后使用该函数去初始化结构体。
而sem_open()
则可以直接返回一个指针,并不需要去创建这个结构体。
基于共享内存的信号量
和共享内存有关了