(1)概述
1.理论与SYSTE-V一样, 但在IPC上进行了封装, 和在文件系统上进行了扩展
2.POSIX 信号量和 System V 信号量的作用是相同的,都是用于同步进程之间及线程之间的操作,以达
到无冲突地访问共享资源的目的。
3.POSIX 信号量的作用和 System V 信号量是一样的。但是两者在接口上有很大的区别:
·POSIX 信号量将创建和初始化合二为一
·一次只能修改一个信号量。System V 信号量其本质是信号量集,一次可以修改多个信号量。
·POSIX 信号量一次只能将信号量的值加 1 或减 1 。System V 信号量能够加上或减去一个大于 1 的值。
·POSIX 信号量并没有提供一个等待信号量变为 0 的接口, System V 信号量中, semop 函数则提供了这样的接口
·POSIX 信号量并没有提供 UNDO 操作,而 System V 信号量则提供了这样的操作。
4.POSIX 信号量真正比 System V 信号量优越的地方在于, POSIX 信号量性能更好。
对于 System V 信号量而言,每次操作信号量,必然会从用户态陷入内核态时间上的开销很大
POSIX 信号量,只要不存在真正的两个线程争夺一把锁的情况,那么修改信号量就只是用户态的操作,并不会牵扯到内核。
在竞争并不激烈的情况下, POSIX 的性能要远远高于 System V 信号量。
5.(☆)POSIX 提供了两类信号量:有名信号量和无名信号量。
1.无名信号量,又称为基于内存的信号量,由于其没有名字,没法通过 open 操作直接找到对应的信号量,
所以很难直接用于没有关联的两个进程之间:无名信号量多用于线程之间的同步。
2.有名信号量由于其有名字,多个不相干的进程可以通过名字来打开同一个信号量,从而完成同步
操作,所以有名信号量的操作要方便一些,适用范围也比无名信号量更广。
(2)有名信号量(用于进程间的通信)
// 有名字,多个不相干的进程可以通过名字来打开同一个信号量,从而完成同步
①创建、打开、关闭、删除有名型号量
1.sem_t *sem_open(const char *name, int oflag);
2.sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
第二个参数 oflag 标志位支持的标志包括 O_CREAT 和 O_EXCL 标志位。如果带了 O_CREAT 标志位,则表示要创建信号量。
mode: 创建的新信号量的访问权限
value: 新建信号量的初始值。创建和赋初值都是由一个接口来完成的
不要尝试创建 sem_t 结构体的副本,切记,后面所有的调用都要用通过 sem_open 返回的 sem_t 类型的指针来进行操作,而不能使用结构体的副本。
3.int sem_close(sem_t *sem); // 关闭信号量
1.当一个进程打开有名信号量时,系统会记录进程与信号的关联关系。调用 sem_close 时,会终止这
种关联关系,同时信号量的进程数的引用计数减 1 。
2.进程终止时,进程打开的有名信号量会自动关闭。当进程执行 exec 系列函数时,进程打开的有名信号量会自动关闭。
3.关闭不等同于删除.
4.int sem_unlink(const char *name); // 删除信号量
将有名信号量的名字作为参数,传递给 sem_unlink ,该函数会负责将该有名信号量删除。由于系统
为信号量维护了引用计数,所以只有当打开信号量的所有进程都关闭了之后,才会真正地删除。
5.注意
如果程序结尾没有删除Posix信号量, 第二次运行时就只会打开前面创建的信号量;
而信号量的初值是在信号量创建时设置的;
==> 第二次运行时,信号量的初值可能不是我们想要的
②信号量的使用
/* 信号量的使用,总是和某种可用资源联系在一起的。创建信号量时的 value 值,其实指定了对应资
源的初始个数。当申请该资源时,需要先调用 sem_wait 函数;当发布该资源或使用完毕释放该资源时,
则调用 sem_post 函数。
*/
1.等待信号量
int sem_wait(sem_t *sem);
如果调用 sem_wait 函数时,信号量的当前值大于 0 ,那么 sem_wait 函数立刻返回。否则 sem_wait 函
数陷入阻塞,待信号量的值大于 0 之后,再执行减 1 操作,然后成功返回。
int sem_trywait(sem_t *sem);
sem_trywait 会尝试将信号量的值减 1 ,如果信号量的值大于 0 ,那么该函数将信号量的值减 1 之后会
立刻返回。如果信号量的当前值为 0 ,那么 sem_trywait 也不会陷入阻塞,而是立刻返回失败,并置 errno
为 EAGAIN 。
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
有限期等待,第二个参数为一个绝对时间。可以使用 gettimeofday 函数获取到 struct timeval 类型的当前时间,然后
将 timeval 转换成 timespec 类型的结构体,最后在该值上加上想等待的时间。
如果超过了等待时间,信号量的值仍然为 0 ,那么返回 -1 ,并置 errno 为 ETIMEOUT 。
2.发布信号量
int sem_post(sem_t *sem);
表示资源已经使用完毕,可以归还资源了。该函数会使信号量的值加 1 。
如果发布信号量之前,信号量的值是 0 ,有多个进程正等待在信号量上,那么将无法确认哪个进程会被唤醒。
3.获取信号量的值
int sem_getvalue(sem_t *sem, int *sval);
返回当前信号量的值,并将值写入 sval 指向的变量
当 sem_getvalue 返回时,其返回的值可能已经过时了。从这个意义上讲,该接口的意义并不大。
(3)无名信号量(用于多线程间的同步互斥)
①为什么要使用无名信号量
1.无名信号量,由于其没有名字,所以适用范围要小于有名信号量。只有将无名信号量放在多个进
程或线程都共同可见的内存区域时才有意义,否则协作的进程无法操作信号量,达不到同步或互斥的目的。
2.所以一般而言,无名信号量多用于线程之间。因为线程会共享进程的地址空间,所以访问共同的无名信号量是很容易办到的事情。
3. 或者将信号量创建在共享内存内,多个进程通过操作共享内存的信号量达到同步或互斥的目的。
②初始化.使用无名信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
pshared: 用于声明信号量是在线程间共享还是在进程间共享。
0 表示在线程间共享,非零值则表示信号量将在进程间共享。
1.要想在进程间共享,无名信号量必须位于共享内存区域内。
2.对于线程间共享的信号量,线程组退出了,无名信号量也就不复存在了。
3.无名信号量初始化以后,就可以像操作有名信号量一样操作无名信号量了。
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem, int *sval);
③销毁无名信号量
int sem_destroy(sem_t *sem);
1.销毁 sem_init 函数初始化的无名信号量。
2.只有在所有进程都不会再等待一个信号量时,它才能被安全销毁。
(4)示例代码
#include
#include
#include
#include
#include
#include
#define CUR printf("cur %d\n", __LINE__)
//#define CUR
#define USED_TO_PTHREADS 0
#define USED_TO_PROCESSS 1
static sem_t sem_product;
static sem_t sem_5;
static sem_t sem_1;
static int val = 0;
void *productor(void *arg)
{
while(1) {
sem_post(&sem_product); // 生产了一个产品
sem_getvalue(&sem_product, &val); // 获取无名信号量的值
printf("prod: %d\n", val);
if (val == 5)
{ // 唤醒阻塞的消费者
sem_post(&sem_5);
// 生产5个后不再生产, 直到消费到只有1个产品
sem_wait(&sem_1);
}
sleep(1); // 每1s生产一个产品
}
pthread_exit(NULL);
}
void *consumer1(void *arg)
{
while(1) {
sem_wait(&sem_5); // 未达到5个之前, 消费者阻塞
sem_wait(&sem_product); // 消费了一个产品
sem_getvalue(&sem_product, &val); // 获取无名信号量的值
printf("cons1: %d\n", val);
if (val == 1)
{ // 唤醒阻塞的生产者
sem_post(&sem_1); // 通知生产
sem_wait(&sem_5); // 阻塞到生产了5个产品
}
sem_post(&sem_5); // 从5个消费到一个的过程中不应该阻塞消费者
sleep(1);
}
pthread_exit(NULL);
}
void *consumer2(void *arg)
{
while(1) {
sem_wait(&sem_5); // 未达到5个之前, 消费者阻塞
sem_wait(&sem_product); // 消费了一个产品
sem_getvalue(&sem_product, &val); // 获取无名信号量的值
printf("cons2: %d\n", val);
if (val == 1)
{ // 唤醒阻塞的生产者
sem_post(&sem_1); // 通知生产
sem_wait(&sem_5); // 阻塞到生产了5个产品
}
sem_post(&sem_5); // 从5个消费到一个的过程中不应该阻塞消费者
sleep(1);
}
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pthread_t thid, thid1, thid2;
// 创建POSIX无名信号量
sem_init(&sem_product, USED_TO_PTHREADS, 0); /* 最开始没有产品 */
sem_init(&sem_5, USED_TO_PTHREADS, 0); /* 最开始没有5个产品 */
sem_init(&sem_1, USED_TO_PTHREADS, 0); /* 最开始没有达到一个产品 */
if(pthread_create(&thid, NULL, productor, NULL))
perror("pthread_create err");
if(pthread_create(&thid1, NULL, consumer1, NULL))
perror("pthread_create err");
if(pthread_create(&thid2, NULL, consumer2, NULL))
perror("pthread_create err");
pthread_join(thid, NULL); // 线程未退出时陷入阻塞
pthread_join(thid1, NULL);
pthread_join(thid2, NULL);
// 销毁无名信号量
sem_destroy(&sem_product);
sem_destroy(&sem_5);
sem_destroy(&sem_1);
return 0;
}
/* 要求: 1.生产5个后广播消费者使用
2.消费到还剩一个后,通知生产者生产
3.生产期间不准消费, 消费期间不准生产
4.生产者0.5s生产一个, 每个消费者1s消费一个
5.使用无名信号量实现
*/
/* 运行结果 :
book@gui_hua_shu:$ ./a.out
// 生产期间, 所有消费者阻塞在sem_5
prod: 1
prod: 2
prod: 3
prod: 4
prod: 5
cons1: 4 // 生产5个后, 生产者阻塞在sem_1, cons1抢夺到sem_5, cons2阻塞
用完后, cons1释放了sem_5
cons2: 3 // 线程cons2抢到了sem_5
用完后, cons2释放了sem_5
cons1: 2
cons2: 1 // cons2释放了sem_1, cons2阻塞在sem_5
// 这时cons1也阻塞在了sem_5
// 循环
prod: 2
prod: 3
prod: 4
prod: 5
.................................*/