一、信号量
线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。
而只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍。而信号量一般常用于保护一段代码,使其每次只被一个执行线程运行。我们可以使用二进制信号量来完成这个工作。
C语言中的信号量分为两种,POSIX信号量和Syetem -V信号量
二、POSIX信号量
信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件semaphore.h中。
1、sem_init函数
该函数用于创建信号量,其原型如下
int sem_init(sem_t *sem,int pshared,unsigned int value);
该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。
pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1.
2、sem_wait函数
该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。它的原型如下:
int sem_wait(sem_t *sem);
等待信号量,如果信号量的值大于0,将信号量的值减1,立即返回。如果信号量的值为0,则线程阻塞。相当于P操作。成功返回0,失败返回-1。sem指向的对象是由sem_init调用初始化的信号量。
3、 sem_post函数
该函数用于以原子操作的方式将信号量的值加1。它的原型如下:
int sem_post(sem_t *sem);
释放信号量,让信号量的值加1。相当于V操作。与sem_wait一样,sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.
4、sem_destroy函数
该函数用于对用完的信号量的清理。它的原型如下:
int sem_destroy(sem_t *sem);
成功时返回0,失败时返回-1.
1 | |
首先创建信号量,第二个参数为0,表示这个信号量是当前进程的局部信号量,初始值为0。
然后使用pthread_create()函数创建两个线程,传入参数a。
线程1创建完成后,由于信号量初始化value=0,调用sem_wait会阻塞这个线程,信号量的值将减少1(此时<0),这个线程函数就会等待。
主线程sleep(10)后,线程2创建并执行调用,sem_post用于使信号量的值增加1,此时线程1可以执行。
使用pthread_join等待所创建的两个线程的结束。
以上代码输出为:
1 | |
可以用来做线程同步使用,比如升级流程代码中,可以在升级的初始化代码中使用sem_wait,最终被阻塞在那里,而用户执行升级命令的流程中才会执行到sem_post函数,从而触发到sem_wait后面的流程继续执行。
三、System -V信号量
1、创建SEM
功能:获取信号量ID
int semget(key_t key,int nsems,int semflg);
返回值:
成功:信号量ID
失败:-1
参数:
key:信号量的键值
nsems:信号量元素的个数
semflg:创建选项
IPC_CREAT:如果key对应的信号量不存在,则创建
IPC_EXCL:如果key对应的信号量存在,则报错
mode:信号量的访问权限
创建信号量时,还受到以下系统信息的影响
SEMMNI:系统中信号量的总数最大值
SEMMSL:每个信号量中信号量元素的个数最大值
SEMMNS:系统中素有信号量中的信号量元素的总数最大值
实例代码:
1 | |
2、PV操作
功能:对信号量进行PV操作,或者等零操作
int semop(int semid,struct sembuf sops[],unsigned nsops);
返回值:
成功:0
失败:-1
参数:
semid:信号量ID
sops:信号量操作结构体数组
nsops结果体数组元素个数
信号量操作结构体
1 | |
根据sem_op的值,信号量操作分为3中情况:
sem_op>0:V操作
sem_op=0:等零操作
sem_op
示例代码
1 | |
3、获取或设置信号量的相关属性
int semctl(int semid,int semnum,int cmd....)
参数:
semid:信号量ID
semnum:信号量元素序号
cmd
IPC_STAT:获取属性信息
IPC_SET:设置属性信息
IPC_RMID:立即删除该信号量,参数semnum将被忽略
IPC_INFO:获得关于信号量的系统限制信息
SEM_INFO:获得系统为共享内存消耗的资源信息
SEM_STAT:获得系统中所有SEM的相关信息
GETALL:返回所有信号量元素的值,参数semnum将被忽略
GETNCNT:返回正阻塞在对该吸纳后量元素P操作的进程总数
GETPID:返回最后一个队该信号量元素操作的进程PID
GETVAL:返回该信号量元素的值
GETZCNT:返回正阻塞对该信号量元素等零操作的进程总数
SETALL:返回所有信号量元素的值,参数semnum将被忽略
SETVAL:设置该信号量元素的值
返回值
GETNCNT:semncnt
GETPID:sempid
GETVAL:semval
GETZCNT:semzcnt
IPC_INFO:内核中记录所有SEM信息的数组的下标最大值
SEM_INFO:同IPC_INFO
SEM_STAT:内核中看i路所有SEM信息的数组
失败:-1
这是一个变参函数,根据cmd的不同,可能需要第四个参数,第四个参数是一个如下所示的联合体,用户需要自己定义
1 | |
当cmd为IPC_STAT或IPC_SET时,需要用到一下属性信息结构体
1 | |
使用例子:
1 | |
四、锁
互斥量从本质上说就是一把锁, 提供对共享资源的保护访问。
1、初始化
在Linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化:
对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER, 或者调用pthread_mutex_init.
对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy.
原型:
`int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
头文件:#include<pthread.h>
返回值: 成功则返回0, 出错则返回错误编号.
说明: 如果使用默认的属性初始化互斥量, 只需把attr设为NULL. 其他值在以后讲解。
2、互斥操作:
对共享资源的访问, 要对互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁. 在完成了对共享资源的访问后, 要对互斥量进行解锁。
首先说一下加锁函数:
头文件:#include<pthread.h>
原型:
1 | |
返回值: 成功则返回0, 出错则返回错误编号.
说明: 具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态。
再说一下解锁函数:
原型:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值: 成功则返回0, 出错则返回错误编号.