前言
信号量,同一时刻有几个线程/进程可以进行访问,可以实现进程/线程之间的同步,互斥访问资源的一种方式
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
(1)P(sv):如果sv的值大于零,就给它减1,往下执行;如果它的值为零,就挂起该进程的执行
(2)V(sv):给sv的值加1,如果有其他进程因等待sv而被挂起,就让它恢复运行。
在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)
信号量和互斥锁(mutex)的区别:互斥锁只允许一个线程进入临界区,而信号量允许多个线程同时进入临界区。
一、POSIX信号量:有名sem_open/无名sem_init
1、有名信号量sem_open()
适合fork进程之间使用
①、创建打开信号量sem_open()
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);/* 这两参数为可选*/
success:返回指向sem_t类型的地址 error: 返回SEM_FAILED
name:信号的名称 注意,不能指定路径名。因为有名信号量,默认放在/dev/shm 里
oflag: 0 访问已有的信号
O_CREAT 不存在则创建,指定这个就需要下面mode/value参数。
O_EXCL| O_CREAT 并且信号量存在,那么会sem_open失败
mode:可选项,权限码 0666
value:可选项,新信号量的初始值
Link with -pthread.
②、关闭sem_close()/删除sem_unlink()信号量
关闭:
#include <semaphore.h>
关闭与这个信号量的关联,并不是删除
int sem_close(sem_t *sem);
success: 0 error: -1
sem: open时返回的描述符
删除:
彻底摧毁这个信号量
int sem_unlink(const char *name);
success: 0 error: -1
name: 信号量的名称
2、无名信号量sem_init()
适合使用在线程之间
①、初始化信号量 sem_init()
int sem_init(sem_t *sem, int pshared, unsigned int value);
success:0 error: -1
sem:指向信号量对象
pshared: 0:用于同一多线程的同步 > 0 用于多个相关进程间的同步配合共享内存使用
value:初始信号量的值
②、摧毁信号量sem_destroy()
int sem_destroy(sem_t *sem);
success:0 error: -1
sem:要销毁的信号量
3、无/有名共用等待( P)sem_wait/发布(V)sem_post()
①、等待信号量,如果信号量的值大于0,将信号量的值减1,立即返回。如果信号量的值为0,则线程阻塞。相当于P操作。
int sem_wait(sem_t *sem);
成功返回0,失败返回-1。
②、释放信号量,让信号量的值加1。相当于V操作
int sem_post(sem_t *sem);
成功返回0,失败返回-1。
4、sem_trywait()/sem_timedwait()
int sem_trywait(sem_t *sem);
sem减一小于0,不阻塞,直接返回,error为EAGAIN
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
等待一段时间,如果这段时间内sem>0返回成功,超过则不阻塞返回error为ETIMEDOUT
5、例子
①、使用无名信号量,对共享变量的访问
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
int number; // 被保护的全局变量
sem_t sem_id1, sem_id2;
void* thread_one_fun(void *arg){
while(1){
sem_wait(&sem_id1);
number++;
printf("thread_one_fun1 number:%d\n",number);
sleep(1);
printf("thread_one_fun2 number:%d\n",number);
sem_post(&sem_id2);
sleep(1);
}
}
void* thread_two_fun(void *arg){
while(1){
sem_wait(&sem_id2);
printf("thread_two_fu1 number:%d\n",number);
sleep(1);
printf("thread_two_fu2 number:%d\n",number);
sem_post(&sem_id1);
sleep(1);
}
}
int main(int argc,char *argv[]){
pthread_t id1, id2;
sem_init(&sem_id1, 0, 1);
sem_init(&sem_id2, 0, 0);
pthread_create(&id1,NULL,thread_one_fun, NULL);
pthread_create(&id2,NULL,thread_two_fun, NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
sem_destroy(&sem_id1);
sem_destroy(&sem_id2);
return 0;
}
②、有名信号量
参考:https://blog.csdn.net/tennysonsky/article/details/46500417
int print1:
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
void print(sem_t *print1, sem_t *print2)
{
int i = 0;
while(1)
{
sem_wait(print1);
i++;
printf("int print1 i = %d\n", i);
sleep(1);
sem_post(print2);
if(i == 5)
break;
}
}
void sem_del(char *name)
{
int ret;
ret = sem_unlink(name);
if(ret < 0)
{
perror("sem_unlink");
}
}
int main(int argc, char **argv)
{
sem_t *print1, *print2;
print1 = sem_open("sem_print1", O_CREAT, 0777, 0);
if(SEM_FAILED == print1)
{
perror("sem_open");
}
print2 = sem_open("sem_print2", O_CREAT, 0777, 1);
if(SEM_FAILED == print2)
{
perror("sem_open");
}
print(print1, print2);
sem_del("sem_print1"); //删除信号量文件sem_print1
sem_del("sem_print2"); //删除信号量文件sem_print2
return 0;
}
int print2:
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
void print(sem_t *print1, sem_t *print2)
{
int i = 0;
while(1)
{
sem_wait(print2);
i++;
printf("in print2 i = %d\n", i);
sleep(1);
sem_post(print1);
if(i == 5)
break;
}
}
int main(int argc, char **argv)
{
sem_t *print1, *print2;
print1 = sem_open("sem_print1", O_CREAT, 0777, 0);
if(SEM_FAILED == print1)
{
perror("sem_open");
}
print2 = sem_open("sem_print2", O_CREAT, 0777, 1);
if(SEM_FAILED == print2)
{
perror("sem_open");
}
print(print1, print2);
return 0;
}
二、System V 信号量
1.信号量创建打开 semget()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
返回标识符
key:所创建或打开信号量集的键值。可为IPC_PRIVATE
nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。
semflg:调用函数的操作类型,也可用于设置信号量集的访问权限,IPC_CREAT |IPC_EXCL |0666
创建信号量集合:
sem_id = semget((key_t)0x1234, (int)ValLen, 0666 |IPC_EXCL | IPC_CREAT);
printf("0InitSemVal,%d\r\n", sem_id);
if(errno == EEXIST)
{
printf("1InitSemVal,%d\r\n", sem_id);
return 1;
}
如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识符,
要么返回系统中已经存在的同样的关键字值的信号量的标识符
2.信号量控制操作semctl()
int semctl(int semid, int semnum, int cmd, .../*union semun arg*/);
功能:控制信号量的信息。
返回值:成功返回0,失败返回-1;
参数:
_semid 信号量的标志码(ID),也就是semget()函数的返回值;
_semnum, 操作信号在信号集中的编号。从0开始。
_cmd 命令,表示要进行的操作。
下面列出的这些命令来源于百度!
参数cmd中可以使用的命令如下:
IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf
参数。
IPC_RMID将信号量集从内存中删除。
GETALL用于读取信号量集中的所有信号量的值。
GETNCNT返回正在等待资源的进程数目。
GETPID返回最后一个执行semop操作的进程的PID。
GETVAL返回信号量集中的一个单个的信号量的值。
GETZCNT返回这在等待完全空闲的资源的进程数目。
SETALL设置信号量集中的所有的信号量的值。
SETVAL设置信号量集中的一个单独的信号量的值。
arg;第4个参数是可选的;semunion :是union semun的实例。
union semun {
int val;
struct semid_ds *buf;
unsigned short *arrary;
};
3.信号量操作semop(P与V)
int semop(int semid ,struct sembuf *_sops ,size_t _nsops);
功能:用户改变信号量的值。也就是使用资源还是释放资源使用权。
返回值:成功返回0,失败返回-1;
参数:
_semid : 信号量的标识码。也就是semget()的返回值。
_sops:是一个指向结构体数组的指针。
struct sembuf{
unsigned short sem_num;//第几个信号量,第一个信号量为0;
short sem_op;//对该信号量的操作。
short _semflg;
};
sem_num: 操作信号在信号集中的编号。第一个信号的编号为0;
sem_op :
如果其值为正数: 该值会加到现有的信号内含值中。通常用于释放所控资源的使用权
如果sem_op的值为负数:而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或
、等于sem_op的绝对值。通常用于获取资源的使用权;
如果sem_op的值为0: 则操作将暂时阻塞,直到信号的值变为0。
_semflg: IPC_NOWAIT //对信号的操作不能满足时,semop()
不会阻塞,并立即返回,同时设定错误信息。累世trylock
IPC_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()
调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源
解锁,造成该资源永远锁定。
nsops:操作结构的数量,恒大于或等于1。