必威体育Betway必威体育官网
当前位置:首页 > IT技术

信号量

时间:2019-07-26 07:41:05来源:IT技术作者:seo实验室小编阅读:66次「手机版」
 

信号量

什么是信号量

信号量主要保护共享资源的,确保该资源在同一时刻只有一个线程占用。

换句话说它就是控制多进程(多线程)共同访问共享资源的一种手段。

信号量的定义

最初的定义:信号量是一个特殊的变量,它只能取正整数值,并且程序对其访问都是原子操作。

正式的定义:它是一个特殊的变量,只允许对它进行等待和发送信号。

信号量就是一种特殊的计数器

当其值>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;

wait_queue_head_t wait; 

}

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删除信号量

 

相关阅读

Linux信号量sem_t

http://blog.csdn.net/evsqiezi/article/details/8061176还没彻底搞懂 继续看

分享到:

栏目导航

推荐阅读

热门阅读