信号量的本质
是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标志,信号量在此过程中负责数据操作的互斥,同步等功能.
一:为什么要使用信号量
为了防止因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任意一时刻只能有一个执行线程访问代码的临界区域.临界区域是指数据更新的代码需要独占式地执行,而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的.其中共享内存的使用就要用到信号量.
二:信号量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的;
P(sv):如果sv的值大于0,就给它减1,如果它的值为0,就挂起还进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起就给它加 1;
三:Linux的信号量机制:
信号量的意图在于进程间同步,互斥锁和条件变量的意图则在于线程间同步.但是信号量也可用于线程间,互斥锁和条件变量也可用于进程间.
进程同步机制:
为了能够有效的控制对个进程之间的沟通过程,保证能够过程有序的进行,操作系统提供了一定同步机制保证进程之间不会自说自话而是有效的协同工作.常见的同步方式有:互斥锁,条件变量,读写锁和信号灯:
四:相关函数:
1:semop函数
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
作用是改变信号量的值
sem_id是返回信号量的标识符
2:semctl函数
int semctl(int semid,int semnum,int cmd,…)
控制信号量的信息
描述
semctl()在semid()标识的信号集上,或者改集合的第semnum个新号量上执行cmd指定的控制命令
调用程序必须按照下面的方式定义这个联合体:
union semun
{
int val;//使用值
struct semid_ds *buf;//IPC_STAT,IPC_SEC使用缓存区
unsigned short*array;//GEETALL,SETALL使用的数组
struct seminfo* __buf;//IPC_INFO(linux特有)使用缓冲区
};
注意:该联合体没有定义在任何系统头文件中,因此的用户自己声明.
五:验证:
1:编写Makefile文件:
.PHONY:all
all:mysem
mysem:mysem.c test.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f mysem
2:mysem.h文件:
#pragma once
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/sem.h>
#define _PATH_NAME_ "/tmp"
#define _PROJ_ID 0x6666
union semun
{
int val;
struct semid_ds *buf;
unsigned short*array;
struct seminfo *_buf;
};
int Createsem(int nums);//创建信号量
int Initsem(int sems_id,int _which,int val);//初始化
int Psem(int sems_id);
int Vsem(int sems_id);
int Destorysem(int sems_id);
int Getsem();
3:mysem.c文件:
#include"mysem.h"
static int Commsemset(int nums,int flags)
{
key_t _key = ftok(_PATH_NAME_,_PROJ_ID);
if(_key<0)
{
printf("%d:%s\n",errno,strerror(errno));
return -1;
}
int sems_id =semget(_key,nums,flags);
return sems_id;
}
static int Comsemop(int sems_id,int op)
{
struct sembuf _sembuf;
_sembuf.sem_num =0;
_sembuf.sem_op =op;
_sembuf.sem_flg =0;
if(semop(sems_id,&_sembuf,1)<0)
{
printf("%d :%s",errno,strerror(errno));
return -1;
}
return 0;
}
int Createsem(int nums)
{
int flags =IPC_CREAT |IPC_EXCL | 0666;
return Commsemset(nums,flags);
}
int Initsem(int sems_id,int _which,int val)
{
union semun _un;
_un.val = val;
if(semctl(sems_id,_which,SETVAL,_un)<0)
{
printf("%d :%s",errno,strerror(errno));
return -1;
}
return 0;
}
int Getsem()
{
int flags =IPC_CREAT;
int nums =0;
return Commsemset(nums,flags);
}
int Destroysem(int sems_id)
{
if(semctl(sems_id,0,IPC_RMID)<0)
{
printf("%d :%s",errno,strerror(errno));
return -1;
}
return 0;
}
int Psem(int sems_id)
{
return Comsemop(sems_id,-1);
}
int Vsem(int sems_id)
{
return Comsemop(sems_id,1);
}
4:test.c测试函数:
#include"mysem.h"
int main()
{
pid_t ip = fork();
if(ip == 0)
{
//child
printf("i am a child. pid is %d\n",getpid());
usleep(313132);
printf("again i am a child. pid is %d\n",getpid());
usleep(3134);
fflush(stdout);
}
else if(ip<0)
{
printf("fork is failed\n");
}
else{
//father
while(1)
{
printf("i am a father:pid is %d\n",getpid());
usleep(43000);
printf("again i am a father:pid is %d\n",getpid());
usleep(420000);
fflush(stdout);
}
pid_t ret =waitpid(ip,NULL,0);
if(ret == ip)
{
printf("wait is success\n");
}
else{
printf("wait failed\n");
}
}
return 0;
}
结果:
总结:
进程/线程同步机制:
临界区(Critical),互斥量(Mutex),信号量(Semaphone),事件(Event)
1:临界区:通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问.在任意时间值允许一个线程对共享资源进程访问,如果多个线程试图访问公共资源,那么一个线程进去后,其他试图进入公共资源的线程将被挂起等待,一直等到临界区的资源被释放后,其他资源在可以抢占.
2:互斥量:采用互斥 ,只有拥有互斥对象的线程才能访问公共资源,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问,互斥不仅negative实现同一程序的公共资源安全共享,还能保证不同应用程序的公共资源安全共享
3:信号量:它允许多个线程在同一时刻访问统一资源,但是需要限制在同一时刻访问此资源的最大线程数目,信号量允许多个线程同时使用共享资源,这与操作系统的Pv操作相同.它指出了同时访问共享资源的线程最大数目.
PV操作及信号量的概念是荷兰科学家提出,信号量S是一个整数,S大于等于0时刻供并发进程使用的资源实体数,但是S小于0时表示正在等待使用共享资源的进程数.
其中:
P操作申请资源:
(1):S减1;
(2):若减1后仍然大于等于0,则进程继续执行
(3):若S减1后小于0,则该进程被阻塞后进入信号行对应的队列中,然后装入进程调度
V操作释放资源:
(1):S加1;
(2):若相加结果大于0,进程继续执行
(3):若相加结果小于等于0,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度
4:事件:通过通知操作的方式保持线程的同步,还可以方便实现对多个线程的优先级比较的操作.