Semaphore
信号量(Semaphore)
线程的信号和进程的信号量类似,使用线程的信号量可以高效地完成基于线程的资源计数。信号量实际上是一个非负的整数计数器,用来实现对公共资源的控制。在公共资源增加的时候,信号量就增加;公共资源减少的时候,信号量就减少;只有当信号量的值大于
sem_t sem_event;
int sem_init(sem_t *sem, int pshared, unsigned int value);//初始化一个信号量
int sem_destroy(sem_t * sem);//销毁信号量
int sem_post(sem_t * sem);//信号量增加1
int sem_wait(sem_t * sem);//信号量减少1
int sem_getvalue(sem_t * sem, int * sval);//获取当前信号量的值
信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是协调共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源
-
测试控制该资源的信号量。
-
若此信号量的值为正,则允许进行使用该资源。进程将信号量减
1 。 -
若此信号量为
0 ,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0 ,进程被唤醒,转入步骤(1) 。 -
当进程不再使用一个信号量控制的资源时,信号量值加
1 。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
信号量与普通整型变量的区别:
-
信号量
(semaphore) 是非负整型变量,除了初始化之外,它只能通过两个标准原子操作:wait(semap), signal(semap) ; 来进行访问; -
操作也被成为
PV 原语(P 来源于Dutch proberen" 测试" ,V 来源于Dutch verhogen" 增加") ,而普通整型变量则可以在任何语句块中被访问;
信号量与互斥锁之间的区别主要在于互斥量用于线程的互斥,信号量用于线程的同步;这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
Semaphore 实现
- count = 10,
10 个座位。 - queue,餐厅位置有限,为了避免混乱,餐厅肯定会吃货们排队。
- wait(),吃货到了餐厅找服务员要位置点餐,这个行为就是
wait 。 - signal(),吃货吃完了买单离开位置,这个行为就是
signal 。
这其实是一个信号量应用的典型场景,这里关键在于正确理解
-
wait():具体到餐厅到例子,
20 个人随机出发去餐厅吃饭,有10 个人先到,然后挨个执行wait 。前10 个人执行wait 的时候是有位置的,所以count>0
,这10 个人每人都消耗掉一个座位开始吃饭。到第11 个人到了都时候,count==0
,没有位置了,所以被suspend ,开始加入排队都队列等待。后续所有人都慢慢的到来,但和第11 个人一样,都只能排队。 -
signal():过了一段时间之后,有个人吃好结账离开了餐厅。这时候如果没有人在排队,位置数量
count++
,没有其它事情发生。但如果有人在排队,比如上面的情况,有10 个人在等待位置,餐厅会把排在第一个的人安排到刚才空出来的位置,count 值没有变化,但队列的人少了一个。
对于
-
wait 和signal 都是原子操作。可以简单理解为上面代码里wait(), signal() 两个函数都是加锁的。这个特性其实让semaphore 的行为变得更简单清晰。大家想象,如果到餐厅的10 个人是同时到达的,但不是依次询问餐厅是否有位置,而是10 张嘴同时说话,同时找餐厅要位置,显然情况会变得复杂不好处理。 -
wait 或者signal 调用的顺序是不确定的。上面的例子中每个人都是随机时间出发,到达餐厅的顺序也是随机的,并不一定先出发的就先到。同理每个人吃饭的时间长短也不一定,有人快有人慢,所以吃好离开餐厅的时间点也是随机的。这里每个人都代表一个线程,因为操作系统线程调度策略导致到底哪个线程先执行也是不确定的。
它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的
通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程
我们看到 信号量 机制是有以下的结构体表示的:
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
自旋锁 的设计理念是它仅会被持有非常短的时间。但持有自旋锁的时候我们不可以进入睡眠模式因为其他的进程在等待我们。为了防止 死锁 上下文交换 也是不允许的。
当需要长时间持有一个锁的时候 信号量 就是一个很好的解决方案。从另一个方面看,这个机制对于需要短期持有锁的应用并不是最优。为了理解这个问题,我们需要知道什么是 信号量。
就像一般的同步原语,信号量 是基于变量的。这个变量可以变大或者减少,并且这个变量的状态代表了获取锁的能力。注意这个变量的值并不限于
第一种 信号量 的值可以为
Semaphore: 信号量
信号量
信号量可以分为几类
信号量通过一个计数器控制对共享资源的访问,信号量的值是一个非负整数,所有通过它的线程都会将该整数减一。如果计数器大于
计数器计算的结果是允许访问共享资源的通行证。因此,为了访问共享资源,线程必须从信号量得到通行证,如果该信号量的计数大于
信 号灯与互斥锁和条件变量的主要不同在于”灯”的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于”等待”操作,即资源不可用 的话,信号灯机制则侧重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持灯亮状 态。当然,这样的操作原语也意味着更多的开销。
信号灯的应用除了灯亮
1. 创建和 注销
int sem_init(sem_t *sem, int pshared, unsigned int value)
这 是创建信号灯的
int sem_destroy(sem_t * sem)
被注销的信号灯
2. 点灯和灭灯
int sem_post(sem_t * sem)
点灯操作将信号灯值原子地加
int semwait(sem_t * sem) int semtrywait(sem_t * sem)
3. 获取灯值
int sem*getvalue(sem_t * sem, int _ sval)
读取
4. 其他
-
创建
Create -
等待
Wait:
线程等待信号量,如果值大于
执行释放信号量,则值加一;如果此时有正在等待的线程,则唤醒该线程。
如果调用
信号量,是可以用来保护两个或多个关键代码段,这些关键代码段不能并发调用。在进入一个关键代码段之前,线程必须获取一个信号量。如果关键代码段中没有任何线程,那么线程会立即进入该框图中的那个部分。一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量,然后将
信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的
信号量的使用特点使其更适用于对