POSIX信号量是属于POSIX标准系统接口定义的实时扩展部分。在SUS(Single UNIX Specification)单一规范中,定义的XSI IPC中也同样定义了人们通常称为System V信号量的系统接口。信号量作为进程间同步的工具是很常用的一种同步IPC类型。

在《UNIX网络编程 卷2:进程间通信》的前言第二页与第1版的区别中作者提到“POSIX IPC函数时大势所趋,因为他们比System V中的相应部分更具有优势”,这里所说的优势我还得慢慢领会呀。。。<T_T>

信号量是一种用于不同进程间进行同步的工具,当然对于进程安全的对于线程也肯定是安全的,所以信号量也理所当然可以用于同一进程内的不同线程的同步。

有了互斥量和条件变量还提供信号量的原因是:信号量的主要目的是提供一种进程间同步的方式。这种同步的进程可以共享也可以不共享内存区。虽然信号量的意图在于进程间的同步,互斥量和条件变量的意图在于线程间同步,但信号量也可用于线程间同步,互斥量和条件变量也可通过共享内存区进行进程间同步。但应该根据具体应用考虑到效率和易用性进行具体的选择。

1 POSIX信号量的操作

POSIX信号量有两种:有名信号量无名信号量,无名信号量也被称作基于内存的信号量。有名信号量通过IPC名字进行进程间的同步,而无名信号量如果不是放在进程间的共享内存区中,是不能用来进行进程间同步的,只能用来进行线程同步。

POSIX信号量有三种操作:

(1)创建一个信号量。创建的过程还要求初始化信号量的值。

根据信号量取值(代表可用资源的数目)的不同,POSIX信号量还可以分为:

  • 二值信号量:信号量的值只有0和1,这和互斥量很类型,若资源被锁住,信号量的值为0,若资源可用,则信号量的值为1;
  • 计数信号量:信号量的值在0到一个大于1的限制值(POSIX指出系统的最大限制值至少要为32767)。该计数表示可用的资源的个数。

(2)等待一个信号量(wait)。该操作会检查信号量的值,如果其值小于或等于0,那就阻塞,知道该值变成大于0,然后等待进程将信号量的值减1,进程获得共享资源的访问权限。这整个操作必须是一个原子操作。该操作还经常被称为P操作(荷兰语Proberen,意为:尝试)。

(3)挂出一个信号量(post)。该操作将信号量的值加1,如果有进程阻塞着等待该信号量,那么其中一个进程将被唤醒。该操作也必须是一个原子操作。该操作还经常被称为V操作(荷兰语Verhogen,意为:增加)

下面演示经典的生产者消费者问题,单个生产者和消费者共享一个缓冲区;

下面是生产者和消费者同步的伪代码:

//信号量的初始化
get = 0;//表示可读资源的数目
put = 1;//表示可写资源的数目 //生产者进程 //消费者进程
for(; ;){ for(; ;){
Sem_wait(put); Sem_wait(get);
写共享缓冲区; 读共享缓冲区;
Sem_post(get); Sem_post(put);
} }

上面的代码大致流程如下:当生产者和消费者开始都运行时,生产者获取put 信号量,此时 put 为 1 表示有资源可用,生产者进入共享缓冲区,进行修改。而消费者获取 get 信号量,而此时 get 为 0 ,表示没有资源可读,于是消费者进入等待序列,直到生产者生产出一个数据,然后生产者通过挂出 get 信号量来通知等待的消费者,有数据可以读。

很多时候信号量和互斥量,条件变量三者都可以再某种应用中使用,那这三者的差异有哪些呢,下面列出了这三者之间的差异

  • 互斥量必须由给它上锁的线程解锁。而信号量不需要由等待它的线程进行挂出,可以在其他进程进行挂出操作。
  • 互斥量要么被锁住,要么是解开状态,只有这两种状态。而信号量的值可以支持多个进程成功进行wait操作。
  • 信号量的挂出操作总是被记住,因为信号量有一个计数值,挂出操作总会将该计数值加1,然而当向条件变量发送一个信号时,如果没有线程等待在条件变量,那么该信号会丢失。

2 POSIX信号量函数接口

POSIX信号量的函数接口如下图所示:

2.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

sem_open用于创建或打开一个信号量,信号量是通过name参数即信号量的名字来进行标识的。关于POSIC IPC的名字可以参考《UNIX网络编程 卷2:进程间通信》P14。

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.2信号量的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是一个绝对时间,具体可以参考条件变量关于等待时间的使用

2.3信号量的V操作
#include <semaphore.h>

int sem_post(sem_t *sem);
//成功返回0,失败返回-1

当一个线程使用完某个信号量后,调用sem_post,使该信号量的值加1,如果有等待的线程,那么会唤醒等待的一个线程。

2.4获取当前信号量的值
#include <semaphore.h>

int sem_getvalue(sem_t *sem,  int *sval);
//成功返回0,失败返回-1

该函数返回当前信号量的值,通过sval输出参数返回,如果当前信号量已经上锁,那么返回值为0,或为负数,其绝对值就是等待该信号量解锁的线程数。

下面测试在Linux下的信号量是否会出现负值:

#include <iostream>

#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);
sleep(10);
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);
}

执行结果如下:

semaphore value:4
semaphore value:3
semaphore value:2
semaphore value:1
semaphore value:0
semaphore value:0
semaphore value:0

这说明在Linux 2.6.18中POSIX信号量是不会出现负值的。

2.5无名信号量的创建和销毁
#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摧毁它。这里要注意的是:摧毁一个有线程阻塞在其上的信号量的行为是未定义的

2.6有名和无名信号量的持续性

有名信号量是随内核持续的。当有名信号量创建后,即使当前没有进程打开某个信号量它的值依然保持。直到内核重新自举或调用sem_unlink()删除该信号量。

无名信号量的持续性要根据信号量在内存中的位置:

  • 如果无名信号量是在单个进程内部的数据空间中,即信号量只能在进程内部的各个线程间共享,那么信号量是随进程的持续性,当进程终止时它也就消失了。
  • 如果无名信号量位于不同进程的共享内存区,因此只要该共享内存区仍然存在,该信号量就会一直存在。所以此时无名信号量是随内核的持续性

2.7信号量的继承和销毁

(1)继承

对于有名信号量在父进程中打开的任何有名信号量在子进程中仍是打开的。即下面代码是正确的:

sem_t *pSem;
pSem = sem_open(SEM_NAME, O_CREAT, 0666, 5); if(fork() == 0)
{
//...
sem_wait(pSem);
//...
}

对于无名信号量的继承要根据信号量在内存中的位置:

  • 如果无名信号量是在单个进程内部的数据空间中,那么信号量就是进程数据段或者是堆栈上,当fork产生子进程后,该信号量只是原来的一个拷贝,和之前的信号量是独立的。下面是测试代码:
int main()
{
sem_t mSem;
sem_init(&mSem, 0, 3); int val;
sem_getvalue(&mSem, &val);
cout<<"parent:semaphore value:"<<val<<endl; sem_wait(&mSem);
sem_getvalue(&mSem, &val);
cout<<"parent:semaphore value:"<<val<<endl; if(fork() == 0)
{
sem_getvalue(&mSem, &val);
cout<<"child:semaphore value:"<<val<<endl; sem_wait(&mSem); sem_getvalue(&mSem, &val);
cout<<"child:semaphore value:"<<val<<endl; exit(0);
}
sleep(1); sem_getvalue(&mSem, &val);
cout<<"parent:semaphore value:"<<val<<endl;
}

测试结果如下:

parent:semaphore value:3
parent:semaphore value:2
child:semaphore value:2
child:semaphore value:1
parent:semaphore value:2
  • 如果无名信号量位于不同进程的共享内存区,那么fork产生的子进程中的信号量仍然会存在该共享内存区,所以该信号量仍然保持着之前的状态。

(2)销毁

对于有名信号量,当某个持有该信号量的进程没有解锁该信号量就终止了,内核并不会将该信号量解锁。这跟记录锁不一样。

对于无名信号量,如果信号量位于进程内部的内存空间中,当进程终止后,信号量也就不存在了,无所谓解锁了。如果信号量位于进程间的共享内存区中,当进程终止后,内核也不会将该信号量解锁。

下面是测试代码:

int main()
{
sem_t *pSem;
pSem = sem_open(SEM_NAME, O_CREAT, 0666, 5); int val;
sem_getvalue(pSem, &val);
cout<<"parent:semaphore value:"<<val<<endl; if(fork() == 0)
{
sem_wait(pSem);
sem_getvalue(pSem, &val);
cout<<"child:semaphore value:"<<val<<endl; exit(0);
}
sleep(1); sem_getvalue(pSem, &val);
cout<<"parent:semaphore value:"<<val<<endl; sem_unlink(SEM_NAME);
}

下面是测试结果:

parent:semaphore value:5
child:semaphore value:4
parent:semaphore value:4

2.8信号量代码测试

对于有名信号量在父进程中打开的任何有名信号量在子进程中仍是打开的。即下面代码是正确的:

对于信号量用于进程间同步的代码的测试,我没有采用经典的生产者和消费者问题,原因是这里会涉及到共享内存的操作。我只是简单的用一个同步文件操作的例子进行描述。 在下面的测试代码中,POSIX有名信号量初始值为2,允许两个进程获得文件的操作权限。代码如下:

#include <iostream>
#include <fstream>
#include <cstdlib> #include <unistd.h>
#include <semaphore.h>
#include <fcntl.h> using namespace std; #define SEM_NAME "/sem_name" void semTest(int flag)
{
sem_t *pSem;
pSem = sem_open(SEM_NAME, O_CREAT, 0666, 2); sem_wait(pSem); ofstream fileStream("./test.txt", ios_base::app); for (int i = 0; i < 5; ++i)
{
sleep(1); fileStream<<flag;
fileStream<<' '<<flush;
} sem_post(pSem);
sem_close(pSem);
} int main()
{
for (int i = 1; i <= 3; ++i)
{
if (fork() == 0)
{
semTest(i); sleep(1);
exit(0);
}
}
}

程序的运行结果,“./test.txt”文件的内容如下:

//./test.txt
1 2 1 2 1 2 1 2 1 2 3 3 3 3 3

Jul 1, 2013 PM 22:04 @dorm

杂谈:

转眼间到7月份了,按着之前的计划,我现在应该在干自己想干的事,可是总是事与愿违,的确很大因素上是自己的原因,哎,苦逼呀。。。只能多看点书,9月份找个好工作吧,希望暑假没有乱七八糟的事烦心。。。

今天是莎姐的生日,老弟这里祝你生日快乐,虽然不能一起帮你过个生日,还是希望你每天都开心快乐,越来越漂亮,爱情甜蜜蜜。。。<^_^>

Linux进程同步之POSIX信号量的更多相关文章

  1. linux网络编程-posix信号量与互斥锁(39)

    -posix信号量信号量 是打开一个有名的信号量 sem_init是打开一个无名的信号量,无名信号量的销毁用sem_destroy sem_wait和sem_post是对信号量进行pv操作,既可以使用 ...

  2. linux POSIX 信号量介绍

    信号量一.什么是信号量信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)使用.多线程可以同时运行多个线程函数完成功能,但是对于共享数据如果不加以锁定,随意改变共享数据的值会发生 ...

  3. Linux进程间通信IPC学习笔记之同步二(Posix 信号量)

    Linux进程间通信IPC学习笔记之同步二(Posix 信号量)

  4. linux Posix 信号量 一

    信号量是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语. linux提供两种信号量,“内核信号量”和“用户态进程信号量”,“用户态信号量”又分为“Posix”,“System V”信号 ...

  5. Linux多线程实践(5) --Posix信号量与互斥量解决生产者消费者问题

    Posix信号量 Posix 信号量 有名信号量 无名信号量 sem_open sem_init sem_close sem_destroy sem_unlink sem_wait sem_post ...

  6. linux Posix 信号量 二

    一.Posix信号量 1.Posix信号量分为两种: 1.   有名信号量:使用Posix IPC名字标识(有名信号量总是既可用于线程间的同步,又可以用于进程间的同步) 2.   内存信号量:存放在共 ...

  7. linux c编程:Posix信号量

    POSIX信号量接口,意在解决XSI信号量接口的几个不足之处: POSIX信号量接口相比于XSI信号量接口,允许更高性能的实现. POSIX信号量接口简单易用:没有信号量集,其中一些接口模仿了我们熟悉 ...

  8. linux网络编程之posix信号量与互斥锁

    继上次学习了posix线程之后,这次来讨论一下posix信号量与互斥锁相关的知识: 跟posix消息队列,共享内存的打开,关闭,删除操作一样,不过,上面的函数是对有名信号量进行操作,通过man帮助可以 ...

  9. linux POSIX信号量

    POSIX信号量机制是3种IPC机制之一,3种IPC机制源于POSIX.1的实时扩展. 创建一个新的命名信号量或者使用一个现有信号量 #include <fcntl.h> #include ...

随机推荐

  1. Wowza流媒体Live直播和VOD点播配置实战

    Wowza是当今可以说最流行的流媒体服务器之一,近来因为需要搭建相应的服务器,但又不想用camera等作真实的直播,所以想办法用媒体文件转换成直播流再提供给Wowza进行直播.这里把该设置步骤以及设计 ...

  2. 用javascript把扑克牌理理顺!

    打扑克的人都知道,比如斗地主! 我们一般都会按照顺序把随机摸过来的牌从小到大的顺序在手上理整齐(记得小时候打牌两副牌手都抓不过来),这篇随笔就是想通过实现这个功能来熟悉下js中排序数组等相关知识. 用 ...

  3. webkit内核下的mouseup后mousemove自动触发问题及解决方法

    如题,就以chrome为代表举例说明遇到mousemove的问题. 为body分别绑定onmousedown.onmousemove.onmouseup,并为触发时打印至控制台.代码如下(同学不要忘记 ...

  4. ANDROID定义自己的观点——模仿瀑布布局(源代码)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 简单介绍: 在自己定义view的时候,事实上非常easy,仅仅须要知道3步骤: 1.測量- ...

  5. Spring AspectJ的Execution表达式-备忘笔记

    Aspectj切入点语法定义 在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点" 例如定义切入点表达式  execu ...

  6. Atitit.升级软件的稳定性---基于数据库实现持久化 循环队列 循环队列

    Atitit.升级软件的稳定性---基于数据库实现持久化  循环队列 环形队列 1. 前言::选型(马) 1 2. 实现java.util.queue接口 1 3. 当前指针的2个实现方式 1 1.1 ...

  7. Lyx输入中文与代码高亮

    如果您看了我的这个随笔:<OpenSUSE 13.2安装Texlive2014+Texmaker+Lyx> (一)LyX中文 打开Lyx直接新建开始使用,那么输入的中文会是编译失败的,疑? ...

  8. 探秘IntelliJ IDEA 13中的版本控制——Subversion 1.8

    IntelliJ IDEA 中引入的重要特性就是版本控制,而在IntelliJ IDEA 13中的体现便是支持最新的Subversion 1.8. 相对于之前版本对Subversion的支持,Subv ...

  9. html5 音频和视频(audio And video)

    1.音频和视频  Web 上的视频 直到现在,仍然不存在一项旨在网页上显示视频的标准. 今天,大多数视频是通过插件(比如 Flash)来显示的.然而,并非所有浏览器都拥有同样的插件. HTML5 规定 ...

  10. CSS知识点:font小细节

    font是用来设置网页字体属性的关键字,使用频率非常高,大家也比较熟悉.它有两种写法,一种是简写,一种分开写. 简写--font:italic bold 12px/20px arial,sans-se ...