您的位置:首页 > 运维架构 > Linux

信号量&同步与互斥

2018-04-08 11:28 225 查看
在介绍信号量&同步与互斥之前,我们先来了解一下生产者与消费者模型。



生产者将数据放入缓冲区,消费者将数据从缓冲区取走消费。


生产者消费者的模型提出了三种关系,两种角色,一个场所

三种关系:

- 生产者之间的互斥关系

- 消费者之间的竞互斥关系

- 生产者和消费者之间互斥和同步关系(同一时刻只能有一个,要么在生产,要么在消费,这就是互斥关系,只能在生产者生产完了之后才能消费,这就是同步关系)

两个角色:一般是用进程或线程来承担生产者或消费者

一个场所:有效的内存区域。(如单链表,数组)

我们就可以把这个想象成生活中的超市供货商,超市,顾客的关系,超市供货商供货,超市是摆放货物的场所,然后用户就是消费的。

信号量主要用于同步和互斥,那么什么是同步与互斥呢?

进程互斥:

由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系称为互斥。

系统中的某些资源一次只允许一个进程使用,这样的资源称为临界资源或者互斥资源。

进程中涉及到互斥资源的程序段叫临界区。

进程同步

进程同步指的是多个进程需要相互配合共同完成一项任务。

司机 p1                               售票员 p2
while(1)                            while(1)
{                                   {
启动车辆;                            关门;
正常运行;                            售票;
到站停车;                            开门;
}                                   }


信号量和P、V原语

信号量和p、v原语由大佬Dijkstra(迪杰斯特拉)提出

信号量

互斥:P、V在同一进程中

同步:P、V在不同进程中

信号量值的含义

s > 0: s表示可用资源的个数

s = 0: 表示无可用资源,无等待进程

s < 0: |s|表示等待队列中进程个数

信号量结构体的伪代码

信号量本质上是一个计数器
struct semaphore
{
int value;
pointer_PCB queue;
}


P原语

p(s){
s.value = s.value--;
if(s.value < 0){
该进程状态设置为等待状态;
将该进程的PCB插入相应等待队列s.queue末尾;
}
}


V原语

V(s){
s.value = s.value++;
if(s.value <= 0){
唤醒相应等待队列s.queue中等待的一个进程
改变其状态为就绪态
将其插入就绪队列
}
}


简单的介绍之后,就让我们来用代码测试一下同步与互斥吧!

这里,我们按照二元信号量来测试。

comm.h

#pragma once

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<unistd.h>
#include<sys/wait.h>

#define PATHNAME "."
#define PROJ_ID 0x6666

union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};

int createSemSet(int nums);
int initSem(int semid, int nums, int initVal);
int getSemSet(int nums);
int P(int semid,int who);
int V(int semid,int who);
int destroySemSet(int semid);


comm.c

#include"comm.h"

static int commSemSet(int nums,int flags){
key_t _key = ftok(PATHNAME,PROJ_ID);
if(_key < 0){
perror("ftok");
return -1;
}
int semid = semget(_key, nums, flags);
if(semid < 0){
perror("semget");
return -2;
}

return semid;
}

int createSemSet(int nums){
return commSemSet(nums, IPC_CREAT|IPC_EXCL|0666);
}

int getSemSet(int nums){
return commSemSet(nums, IPC_CREAT);
}

int initSem(int semid, int nums, int initVal){
union semun _un;
_un.val = initVal;
if(semctl(semid, nums, SETVAL, _un) < 0){
perror("semctl");
return -1;
}

return 0;
}

static int commPV(int semid, int who, int op){
struct sembuf _sf;
_sf.sem_num = who;
_sf.sem_op = op;
_sf.sem_flg = 0;
if(semop(semid, &_sf, 1) < 0){
perror("semop");
return -1;
}
return 0;
}

int P(int semid, int who){
return commPV(semid, who, -1);
}

int V(int semid, int who){
return commPV(semid, who, 1);
}

int destroySemSet(int semid){
if(semctl(semid, 0, IPC_RMID) < 0){
perror("semctl");
return -1;
}
return 0;
}


test_sem.c

#include"comm.h"

int main(){
int semid = createSemSet(1);
initSem(semid, 0, 1);
pid_t id = fork();
if(id == 0){
int _semid = getSemSet(0);
while(1){
P(_semid,0);
printf("A");
fflush(stdout);
usleep(123456);
printf("A");
fflush(stdout);
usleep(321456);
V(_semid, 0);
}
}
else{
while(1){
P(semid, 0);
printf("B");
fflush(stdout);
usleep(223456);
printf("B");
fflush(stdout);
usleep(12346);
V(semid, 0);
}
wait(NULL);
}
destroySemSet(semid);
return 0;
}


最后奉上Makefile

test_sem:comm.c test_sem.c
gcc -o $@ $^

.PHONY:clean
clean:
rm -f test_sem


此时,显示器只有一个,两个进程同时打印,显示器称为临界资源,使用二元信号量(互斥锁)进行保护

执行效果如下:



所有AB都是成对出现,不会出现交叉的情况,那如果去掉PV呢?



出现打印的内容相互影响

在ctrl + c之后使用ipcs -s 查看信号量,及ipcsrm -s + semid删除信号量

关于ipcs&ipcrm的使用,参照我得博客:ipcs和ipcrm的使用

注意,删除信号量不是必须通过手动删除,这里只是为了演示相关指令,删除IPC资源是进程应该做的事。

由于个人能力有限,以上的说明可能有Bug或者有什么不妥之处,欢迎发邮件到我的邮箱( Cyrus_wen@163.com )批评指正!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息