linux POSIX 信号量介绍

时间:2022-03-23 15:17:15

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

本文主要介绍Linux下的 POSIX信号量:有名信号量和无名信号量
有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步;
无名信号量,其值保存在内存中,故只用于线程的同步,多用共享内存。

POSIX信号量包含一个非负整型变量,并且带有两个原子操作wait 和signal。wait 还可以被称为down、P 或lock,signal 还可以被称为up、V、unlock 或post。在Uinx的API中用的是wait和post。

如果信号量的非负整形变量S大于零,wait就将其减1,如果S 等于0,wait 就将调用线程阻塞。对于post 操作,如果有线程在信号量上阻塞(此时S 等于0),signal就会解除对某个等待线程的阻塞,使其从wait 中返回,如果没有线程阻塞在信号量上,post 就将S加1。

简单来说即:当S大于0时,线程正常运行不会阻塞;当S=0时,线程阻塞直到有post操作将其+1。假设S值设为1,信号量可实现互斥的功能。

信号量的一般操作是 创建-->PV操作-->删除

二. 无名信号量
头文件: <semaphore.h>
int sem_init(sem_t *sem, int pshared,unsigned value);
功能:初始化指定的信号量
返回值:成功 0 ; 错误 错误号。
参数说明:sem:信号量变量名;参数value为信号量的初始值。
参数pshared用于说明信号量的共享范围,如果pshared为0,那么该信号量只能由初始化这个信号量的进程中的线程使用,如果pshared非零,任何可以访问到这个信号量的进程都可以使用这个信号量。
必须保证只调用sem_init()进行初始化一次,对于一个已初始化过的信号量调用sem_init()的行为是未定义的

int sem_destroy(sem_t *sem);
功能:销毁一个指定的信号量
返回值:成功 0 ; 错误 错误号。
参数说明:sem:信号量变量名(sem_init内值)
摧毁一个有线程阻塞在其上的信号量的行为是未定义的。

P操作:
int sem_trywait(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_timedwait(sem_t * sem, const struct timespec time);

减1信号量的值。信号量值为0时,sem_wait会阻塞,直到成功使信号量减1或者被信号中断时才返回;
sem_trywait为非阻塞版本,当信号量为0时,该函数返回-1并且将errno置为EAGAIN
sem_timedwait带有超时功能,超时到期并且信号量计数没能减1,sem_timedwait将返回-1且将errno设置为ETIMEDOUT。(毕竟一直阻塞也不是办法。。)

V操作:
int sem_post(sem_t *sem);
信号量加1,若此时有sem_wait正在阻塞则唤醒执行。

取值操作:
int sem_t getvalue(sem_t sem, int *val);
获取信号量的值,一般只用来调试,打印val查看。

三. 有名信号量
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
功能:创建或打开一个信号量
返回值:信号量指针,该指针可供其他对该信号量进行操作的函数使用
参数说明:name:信号量的名字,标识信号量;
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。

int sem_close(sem_t *sem);
功能:sem_close用于关闭打开的信号量。当一个进程终止时,内核对其上仍然打开的所有有名信号量自动执行这个操作。
返回值:成功返回0,失败返回-1
调用sem_close关闭信号量并没有把它从系统中删除它,POSIX有名信号量是随内核持续的。即使当前没有进程打开某个信号量它的值依然保持。直到内核重新自举或调用sem_unlink()删除该信号量。

int sem_unlink(const char *name);
功能:将有名信号量立刻从系统中删除,但信号量的销毁是在所有进程都关闭信号量的时候(只close是不够的!)
返回值:成功返回0,失败返回-1

有名信号量的PV操作与无名信号量类似。

 

参考网上代码,自测了使用信号量同步线程的功能:

 1 sem_t sem_g, sem_p;
 2 int g_i_res=1;
 3 
 4 void *pid_g(void)
 5 {
 6     while(1)
 7     {
 8         sem_wait(&sem_g);
 9         g_i_res++;
10         Sleep(1);
11         if(g_i_res>50)
12             system("pause");
13         sem_post(&sem_p);
14     }
15 
16 }
17 
18 void *pid_p(void)
19 {
20     while(1)
21     {
22         sem_wait(&sem_p);
23         printf("%d ", g_i_res);
24         fflush(stdout);
25         sem_post(&sem_g);
26 
27     }
28 
29 }
30 
31 int main()
32 {
33 
34     pthread_t tid1, tid2;
35     sem_init(&sem_g, 0, 0);
36     sem_init(&sem_p, 0, 1);
37 
38     pthread_create(&tid1, NULL, pid_g, NULL);
39     pthread_create(&tid2, NULL, pid_p, NULL);
40 
41     pthread_join(tid1, NULL);
42     pthread_join(tid2, NULL);
43 
44 
45     return 0;
46 }

说明:头文件按需添加,一般有<pthread.h>  <semaphore.h>  ,由于虽然是gcc编译但在Windows环境,所以用了<windows.h>的Sleep()

linux POSIX 信号量介绍

自己运行可以看到数字依次递增的打印过程,若不加system(pause)会无限刷屏的。。。

 

遇到新内容再更新吧~