1.信号量
信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。
2.信号量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
(1)P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
(2)V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)
注:原子操作:单指令的操作称为原子的,单条指令的执行是不会被打断的
3.二元信号量
二元信号量(Binary Semaphore)是最简单的一种锁(互斥锁),它只用两种状态:占用与非占用。所以它的引用计数为1。
4.进程如何获得共享资源
(1)测试控制该资源的信号量
(2)信号量的值为正,进程获得该资源的使用权,进程将信号量减1,表示它使用了一个资源单位
(3)若此时信号量的值为0,则进程进入挂起状态(进程状态改变),直到信号量的值大于0,若进程被唤醒则返回至第一步。
注:信号量通过同步与互斥保证访问资源的一致性。
5.一个demo
一个文件原来里面写了个1,然后有二十个进程,并发的对该文件取出该数,然后+1 写回
#define PROCNUM 20
#define FILENAME "log"
#define LINESIZE 1024
int semid;
void P(void)
{
struct sembuf op;
//对semid的数组下标为0 进行操作
//每次操作资源总量-1
//特殊要求没有
op.sem_num = 0;
op.sem_op = -1;
op.sem_flg = 0;
while(semop(semid, &op, 1) < 0)
{
if(EINTR != errno || EAGAIN != errno)
{
perror("semop()");
exit(1);
}
}
}
void V(void)
{
struct sembuf op;
//对semid的下标为0的位置 进行操作
//每次操作对资源总量+1
//特殊要求没有
op.sem_num = 0;
op.sem_op = 1;
op.sem_flg = 0;
if(semop(semid, &op, 1) < 0)
{
perror("semop()");
exit(1);
}
}
void addFun(void)
{
FILE *fp = fopen(FILENAME,"r+");
char Buf[LINESIZE];
if(NULL == fp)
{
perror("fopen()");
exit(1);
}
P();
fgets( Buf, LINESIZE, fp);
fseek(fp, 0, SEEK_SET);
sleep(1);
fprintf(fp, "%d\n", atoi(Buf)+1);
fflush(fp);
V();
fclose(fp);
}
int main()
{
int i, pid;
int res;
//父子进程之间匿名
semid = semget(IPC_PRIVATE, 1, 0600);
if(semid < 0)
{
perror("semget()");
exit(1);
}
//对数组下标为0的 设置资源总量为1
res = semctl(semid, 0, SETVAL , 1);
if(res < 0)
{
perror("semctl()");
exit(1);
}
for(i = 0 ; i < PROCNUM; i++)
{
pid = fork();
if(pid < 0)
{
perror("fork()");
exit(1);
}
if(0 == pid)
{
addFun();
exit(0);
}
}
for(i = 0; i < PROCNUM; i++)
{
wait(NULL);
}
semctl(semid, 0, IPC_RMID);
return 0;
}