信号量
什么是信号量
信号量主要保护共享资源的,确保该资源在同一时刻只有一个线程占用。
换句话说它就是控制多进程(多线程)共同访问共享资源的一种手段。信号量的定义
最初的定义:信号量是一个特殊的变量,它只能取正整数值,并且程序对其访问都是原子操作。
正式的定义:它是一个特殊的变量,只允许对它进行等待和发送信号。
信号量就是一种特殊的计数器
当其值>0时,其表示可用临界资源的个数
当其值<0,表示资源忙,至少有一个线程在等待。
信号量工作原理
先看计数器计数的方式:
自增型:开始计数为0,来一个自增1,走一个自减1,到计数最大值不允许再来
自减型:开始计数 就是最大值,来一个自减1,走一个自增1,计数到0不允许再来
信号量采用的是自减型,这里说下我个人的理解:(MAX为最大值)
1)我们一般关心资源占用完了没,而不是这个资源一共可以被几个进程占用。所以我们关心的是什么时候不允许再来。
显然自减型判断是否为0更为方便。
我们可以用公共停车场来解释
公共停车场 :信号量控制访问的临界资源的个数
车位:临界资源
想要停车的车辆:想要访问临界资源的线程
计数器:信号量
此刻停车场共有4个停车位,3个停车位分别被车D ,车F,车E占用了,还剩下车位4此刻没被占用。
理解为:信号量管理了4个临界资源,三个临界资源此刻被占用了,一个没被占用。。
现在车A 车B 车C都想进去停车,但是车位只剩下一个。出现了一种“竞态”,谁先抢到计数器发的停车卡谁就可以进去。
进程A 进程B 进程C 同时想对信号量进行P操作,这一步是原子操作,谁先操作成功就可以进入临界区访问临界资源,其中一个进程完成了p操作后,其他两个进程会被挂起,存放入等待队列中,当信号量又>0时,排在前面的那个进程又会被唤醒,又进行p操作。信号量分类
1)内核信号量,由内核控制路径使用
2)用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEMV信号量。
POSIX信号量又分为有名信号量和无名信号量。
有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。
无名信号量,其值保存在内存中。
内核信号量
内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。然而,当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。只有在资源被释放时,进程才再次变为可运行。只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内核信号量。
自旋锁是相当于数据被锁住,就反复执行一条指令(个人理解类比于代码中while(1)死循环),不是睡眠,所以它能处理中断处理的程序。
自旋锁只能一个进程持有。
信号量可以多个进程持有。
自旋锁数据被锁住,等待的进程(线程)就会反复的执行一条指令,对cpu资源的占用,所以适合短时间持有锁。
信号量<0进程会被挂起,和放入等待队列,>0时候进程会被唤醒。所以是函数准备和收尾开销比较大,适合长时间持有锁。
这个是内核信号量的结构体
struct semaphore
{
atomic_t count;
int sleepers;
}
count是信号量的值,>0代表代表有闲资源,=0代表忙资源, <0代表有至少一个等待进程。
sleepers是一个标志,代表是否有一些进程在信号量上睡眠
wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。
内核信号量的函数
初始化信号量
void sema_init (struct semaphore *sem, int val);
把val值初始化信号量sem;
获取信号量
void down(struct semaphore * sem);
这个函数是获得信号量sem,可能会引起睡眠,所以不可以中断上下文使用该函数,它将sem的值减少1,如果该值为非负,直接返回,否则该进程被挂起,直到有进程释放了该信号量,唤醒该进程。
int down_interruptible(struct semaphore * sem);
会被信号打断,正常返回0,打断返回-EINTR
int down_trylock(struct semaphore * sem);
它会去获取信号量,如果立即获得则返回0,没有则返回非0,不会被睡眠,所以可以被中断上下文使用。
释放信号量
void up(struct semaphore * sem);
用户信号量
POSIX信号量
(1)无名信号量
无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程(线程)的共享变量,这两个条件是缺一不可的。
这里可以理解为就是我们创建一个信号变量sem_t sem;那么这个变量必须是多线程所的共享的变量。这也是为什么常用于多线程,因为一个进程里的多线程中全局变量,栈区堆区,文件描述符是共享的。很容易创建这个共享的信号量。
无名信号量的函数
int sem_init(sem_t *sem, int shared, unsigned int value);
sem是信号量的指针;
share = 0 代表是在同一进程里的多线程,
value初始化值;
int sem_wait(sem_t wait);
申请访问 相当于 p操作;
int sem_post(sem_t *sem);
释放资源 相当于 v操作;
我们看个例子主线程把输入字符传入给线程id,线程把字符串大写变小写,写入a.txt
无名就是这么简单的操作,但是也有明显的缺点,就是要共享这个sem信号量。
如果在多进程中使用无名信号量
很简单创建一个信号量,然后fork()一下,当然只要在血缘的进程都可以使用,也暴露了其局限性
(2)有名信号量
有名信号量就是相当于用sem_open函数 代替 无名的sem_init函数
sem_wait 和 sem_post 是和无名共享的
还有个关闭信号量的函数sem_close()
sem_t *sem_open(const char *name,int oflag,mode_t mode,int value);
sem_t sem = sem_open(SEM_NAME, O_CREAT, 0644, 1);
各位看下就大致知道参数的意思了
name:是文件路径名,linux信号量是直接存在/dev/shm 你写个name相当于/dev/shm/sem.name 默认帮你保存在这个路径
你只要写好.后面的name,这个你自己取个后缀名。
mode :权限
value:信号量初始值
SYSTEMV信号量
这里操作的是信号量集,不是单个信号量了,要用到的头文件<sys/ipc.h>
信号量集
信号量集和信号集没有一点关系,首先信号集是信号的集合,它用的是sigset_t类型的变量的位来保存每一个信号,而信号量集是信号量的集合,它是用数组每个元素来保存每个信号量。信号是通知进程间事件的发生,信号量是控制进程间同步,或者线程间同步的一种手段。
SYSTEMV信号量函数
fsemget(key_t key, int nsems, int oflag)
key 用来大家都共同使用某个key,大家都能定位到一个信号量上,这样就能达到信号量在进程间共享。
nsems 代表着信号量的个数,信号量集创建一旦完成就不得更改其个数,除非删除重新创建,或者重新创建一个新的信号量集。
oflag 指定该信号量的权限
int semctl(int semid, int semnum, int cmd,...)
semid 是信号量
semnum 是信号量在集合中的序号
semun 是一个结构体
union semun
{
int val;
struct semid_ds *buf;
ushort *array;
}
这个函数有两个作用一个是初始化信号量,一个是删除信号量
我们看一下这个函数分别是怎么使用这两个作用的
cmd取SETVAL,可以给每个信号量赋值
第四个参数是初始值
semop函数
struct sembuf
{
unsigned short sem num; //操作信号量的下标
short sem_op; //对信号量操作方式 , -1,0, 1
short sem_flg; // 计数为0是否阻塞,为0就阻塞,为IPC_NOWIT就阻塞
}
如何创建信号量集
1)使用fotk或者头文件,生成一个key
2)使用semget创建/获取信号量集
3)使用semtcl给该信号量集中每个信号赋值
4)使用信号量,semop函数
5)使用semtcl删除信号量
相关阅读
http://blog.csdn.net/evsqiezi/article/details/8061176还没彻底搞懂 继续看