非常非常非常.....的重要
在共享内存的代码里面p1.c实质是有问题
lt._flag = 1;//这里先置1
if(c == 'Q')
sprintf(lt._buf,"quit");
else
sprintf(lt._buf,"大家好,%d 我系渣渣辉. %d 是兄弟就来砍我吧!!! %d",i,i+1,i+2);
while(*((int *)shmptr));//如果别人没有把上一条消息给拿走,那我就卡在这里
memcpy(shmptr,<,sizeof(lt));//copy到共享内存 我们是想copy之后别人拿这个信息
我们是想copy之后别人拿这个信息 --- copy之后
你根本就没有告诉别人copy完是什么时候
那么别人就可以在你copy中途的就过来拿
p2 -> lt._flag等于1了就可以拿了
lt._flag等于1是在copy的时间段里面前面那个时间就会将其置1
p2看到变成1了,它就可以去读后面内容了,p2很有可能拿到了一个错误的内容
现在我要解决这个问题
lt._flag = 0;//
if(c == 'Q')
sprintf(lt._buf,"quit");
else
sprintf(lt._buf,"大家好,%d 我系渣渣辉. %d 是兄弟就来砍我吧!!! %d",i,i+1,i+2);
while(*((int *)shmptr));//如果别人没有把上一条消息给拿走,那我就卡在这里
memcpy(shmptr,<,sizeof(lt));//copy到共享内存 我们是想copy之后别人拿这个信息
*(int *)shmptr = 1;//再置1
又有一个新的问题出现了,出现了p3 也操作这个共享内存,它也是往这个共享内存里面写的
p1正在写的途中,p3过来一看 _flag == 0它也可以写
看谁搞得慢,谁慢就保留谁的信息
上面不管有几个进程在活动,不变的只有一个 --- 共享内存不变
那么我可以对这个共享内存实现一个保护机制 --- 只要有进程在操作这个共享内存,别人都不能操作
int flag;//这个flag在共享内存上面,有别与上面共享内存
每一个进程在放在共享内存的时候都先过来看看flag的值
如果flag == 0 ,我就不能访问共享内存
flag == 1,我就可以去访问,访问之前我先将flag调成0,然后访问共享内存
访问完毕再调成1
每一个进程在访问之前都过来看这个flag,都遵守这个规则,那么就不会出现问题了
p1 和 p2同时过来看flag的值,他们两个同时发现flag == 1
他们就去访问这个共享内存了,这个保护机制又崩溃了
要将保护机制继续完善,谁过来访问flag的时候其它的人都不能访问
如果你再弄一个保护的flag1,flag1又可能要成为保护的对象,这就成了一个死局了
我们只需要解决不能同时过来操作flag的值 --- 原子操作可以解决(cpu产生的指令只会允许有一个进程过来操作)
解决这个保护问题,并且实现原子操作的事情就是信号量解决的问题
信号量就是一个以原子操作来实现的一个保镖
保证我们的共享资源有序访问
它是为了保护别人而生的,没有保护对象就不需要信号量
信号量是一种保护机制,对程序员的一个约定(程序员应该遵守,不是强制,程序员要知道如果你不遵守,你很有可能
得到一个错误的值)
因此在并发里面信号量机制成为了必然
信号量的操作流程
首先实现 P操作(上锁,上锁的过程是原子操作)
上锁完毕,再过去访问共享资源
共享资源访问完毕 -> 这个区域我们叫临界区(约定:快进快出)
如果你占着这个临界区,结果你死了或者一直不进行V操作,这种
情况我们叫死锁(死锁是一个非常严重的问题,我们不应该有死锁的操作)
再进行 V操作(解锁)
P操作:使其信号量递减的操作
V操作:使其信号量递增的操作
如我的flag = 1;
P操作就是--flag;
V操作就是++flag
flag = 1 ->这种信号量我们叫互斥信号量
但是有的时候某一些共享资源支持多个进程同时访问,那么这个时候flag > 1
P操作一般我们认为是 --就可以了,但是
有的时候可能一个进程需要多个资源,这个时候就不是--,它需要 -= n
V操作有的时候就需要 += n
信号量实现 --- 它是实现在内核上面的一个标志,对于它的操作保证了原子操作
信号量有两种
System V semaphore -> 两种标准 此标准一般用于进程
System V信号量是一个信号量集,里面有多个信号量
信号量集里面的信号量可以单独操作的,实现对于不同共享资源或者
同一个共享资源需要多个信号量的保护的需求
POSIX semaphore -> 此标准一般用于线程
struct semid_ds {
struct ipc_perm sem_perm; /* 权限 */
__kernel_time_t sem_otime; /* 最后 semop 时间 */
__kernel_time_t sem_ctime; /* 最后 semctl() 时间 */
struct sem *sem_base; /*信号量数组 */ = malloc(sizeof(struct sem) * nsems)
struct sem_queue *sem_pending; /* 等在这个信号量队列上面的进程 */
struct sem_queue **sem_pending_last; /* 最后等的那个 */
struct sem_undo *undo; /* 撤销 */
unsigned short sem_nsems; /* 信号量的个数,就是 sem_base有多少个元素*/
};
struct sem//单个信号量的类型
{
int value;//信号量的值
pid_t sempid;//最后一次进行p/v操作的进程id
int semncnt;//使其增长的进程数量
int semzcnt;//使其变成0的进程数量
};
创建一个信号量集
semget - get a System V semaphore set identifier
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
key:一个ipc key,如果你想操作同一个信号量,key必须一样
nsems:这个信号量集里面你想有几个信号量,只有创建信号量集的时候有效
打开的时候这个参数会被忽略
这个数量一旦确定了,以后就不能改了
semflg:标志
IPC_CREAT | 权限 创建
0打开一个信号量集
返回值:
成功返回信号集的id,失败返回-1,同时errno被设置
The values of the semaphores in a newly created set are indeterminate.
新创建出来的信号量集里面的信号量的值是不确定的
所以我们需要给这些信号量马上设置值
NAME
semctl - System V semaphore control operations
控制这个信号量集
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
semid:信号量集的id
semnum:信号量集里面有多个信号量,你现在要操作哪一个你是不是要给我指明
从0开始 一直到 nsems - 1
如果cmd表明的是操作所有的信号量,那么这个参数就被忽略了
cmd:命令号
IPC_STAT 复制
IPC_SET 设置
IPC_RMID 删除
GETALL 获取所有
GETVAL 获取一个
SETALL 设置所有
SETVAL 设置一个
......
...(arg):根据第三个命令号来,命令不一样,这个参数就会不一样
cmd == GETALL: 获取所有的信号量的值
因此第四个参数就需要弄一个什么样子的东西进去,用于保存所有的信号量的值
我们需要用一个
unsigned short arr[nsems];//原先创建的时候nsems是多少,这里就给多少
semctl(semid,0,GETALL,arr);
cmd == GETVAL : 获取单个信号量的值
第二个参数为哪一个信号量
第四个参数就被忽略了,获取到的值通过返回值返回给你
unsigned short value = semctl(semid,2,GETVAL);//获取信号量集里面的第三个信号的值
cmd == SETALL : 设置所有的信号量的值,第二个参数被忽略
第四个参数就是unsigned short数组的首地址
unsigned short arr[5] = {3,2,5,7,1};//你的信号量集里面有5个信号量
semctl(semid,2,SETALL,arr);
cmd == SETVAL : 设置某一个信号量的值
第二个参数为哪一个信号量
第四个参数就是一个int值
int value = 4;
semctl(semid,1,SETVAL,value);//设置信号量集里面的第二个信号量的值为4
cmd == IPC_STAT/IPC_SET 获取或者设置信号量集头节点的信息
第四个参数就是struct semid_ds的指针,第二个参数会被忽略
struct semid_ds buf;
semctl(semid,0,IPC_STAT,&buf);//将头部信息弄到buf里面去
buf里面改一些什么东西了,然后想将buf设置回去
semctl(semid,0,IPC_SET,&buf);
cmd == IPC_INFO
struct seminfo __buf;
semctl(semid,0,IPC_INFO,&__buf);
.......
由于第四个参数比较多变,为了统一这个参数,可以按照如下共同体建立一个结构体
什么时候用哪一个就行
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
cmd == SETALL,你怎么用这个共同体给我解决
union semun hehe;
hehe.array = malloc(sizeof(unsigned short) * nsems);//nsems就是信号量的个数
//将值赋值到这个数组里面去
hehe.array[0] = 3;
hehe.array[1] = 1;
....
hehe.array[nsems - 1] = n;
semctl(semid,2,SETALL,hehe.array);
SEMOP(2) Linux Programmer's Manual SEMOP(2)
NAME
semop, semtimedop - System V semaphore operations
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
struct sembuf arr[3];......
int semop(int semid, struct sembuf *sops, size_t nsops);
semid:你要操作哪一个信号集
sops:操作的信息 有下面的结构
struct sembuf{
unsigned short sem_num; /* 你要操作信号量集里面的那一个信号量 */
short sem_op; /* 信号量的操作 P/V */
P就是让信号量的值递减
V就是让信号量的值递增
所以这里填负数就是 P -3 -> 将信号量的值 -= 3
所以这里填正数就是 V +3 -> 将信号量的值 += 3
short sem_flg; /* 操作标志 */
0 -> 阻塞
IPC_NOWAIT -> 非阻塞
SEM_UNDO 撤销
这个标志很有意义,当有这个标志之后内核就会关注这个进程
如果进程没有解锁就退出了,内核就会将这个进程操作的信号量的
值恢复到P之前 -- 防止带锁退出而出现的死锁问题
};
nsops:sops这个玩意儿是一个数组的首地址,因此我需要将数组的元素个数传进去
可能操作多个信号量
int semtimedop(int semid, struct sembuf *sops, size_t nsops,
const struct timespec *timeout);//这个函数多了一个timeout的操作
//表示限时等待
semop这个函数如果是阻塞上锁,别人没有释放锁的时候,它是上不上去的,这个时候就会卡在semop
semtimedop给了一个时间,这个时间到了就不等了
struct timespec {
__kernel_time_t tv_sec; /* 秒 */
long tv_nsec; /* 纳秒 */
};
//假设你要等 1s 500000ns的时间
struct timespec tp;
clock_gettime(CLOCK_REALTIME,&tp);//获取现在的时间
//往后等1s 500000ns的时间
tp.tv_sec += 1;
tp.tv_nsec += 500000;
if(tp.tv_nsec > 1000000000)//进1了
{
tp.tv_sec++;
tp.tv_nsec -= 1000000000;
}
semtimedop(,,,&tp);
返回值:
成功0,失败-1,同时errno被设置
死锁问题:只能避免死锁,如果出现死锁你是你的代码有问题,我们需要需要解决这个bug
如果有一个变量i = 1;
进程1对这个i++;进程2对这个i--
两个进程不知道什么时候会运行,请问,当两个进程都运行完一遍之后这个i的值为多少
进程1对i的操作可以分为三步:1读取i的值放在自己的内存空间 2对内存进行++ 3将结果写入到i的内存空间(这三步是原子操作)
进程2对i的操作可以分为三步:4读取i的值放在自己的内存空间 5对内存进行-- 6将结果写入到i的内存空间(这三步是原子操作)
145623 -> 2
123456 -> 1
456123 -> 1
412356 -> 0
451236 -> 0
POSIX semaphore -> 单个信号量
有名信号量:内容在内核里面,在文件系统里面有一个名字
因此可以用于不同的进程间或者线程间
无名信号量:没有名字,因此只能通过遗传,因此只能用于有亲缘关系的进程间
如果这个无名信号量存在于父进程的内存空间,当子进程拷贝过去之后,父子里面的信号量就变成两个了
这个时候父进程就会操作父进程,子进程操作的就是子进程的,这样就起不到保护的作用了
那么我们就需要让这个信号存在于共享内存里面 --- 这种操作明显很麻烦,不建议使用
或者线程间
有名信号量:需要创建或者打开
NAME
sem_open - initialize and open a named semaphore
SYNOPSIS
#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);//纯粹的打开一个信号量
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);//这个玩意可以打开或者创建一个信号量
name:一个存在的路径名 这个路径名里面只能有一个 /,开头也是它
"/hehe.sem"
oflag:标志
0 打开
O_CREAT 创建标记
mode:你创建的时候需要这个权限
0664
value:创建的时候才会有值的设置,如果是打开一个信号量是没有值的设置的
只有创建成功才会被设置进去
返回值:
成功返回一个sem_t指针,指向我们的信号量
失败返回SEM_FAILED,同时errno被设置
Link with -pthread.//链接这个库 pthread 因此编译的时候需要在后面加上 -lpthread
无名信号量只能初始化
NAME
sem_init - initialize an unnamed semaphore
SYNOPSIS
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem:保存我们的信号量
pshared:共享方式
0 :进程的内部线程共享
1 : 不同进程间的共享 ---- 这种不建议使用
value:值
成功0 失败-1
Link with -pthread.
P操作
NAME
sem_wait, sem_timedwait, sem_trywait - lock a semaphore 上锁
SYNOPSIS
#include <semaphore.h>
int sem_wait(sem_t *sem);//等待版本 P操作
int sem_trywait(sem_t *sem);//尝试版本 非阻塞 P操作
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);//限时等待版本 P操作
sem:你要对哪个信号量进程P操作
Link with -pthread.
V操作
NAME
sem_post - unlock a semaphore 解锁
SYNOPSIS
#include <semaphore.h>
int sem_post(sem_t *sem);
sem:你要对哪个信号量进程P操作
Link with -pthread.
POSIX信号量的其它操作
NAME
sem_getvalue - get the value of a semaphore
获取信号量的值
SYNOPSIS
#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
sem:你要对哪个信号量进程进行操作
sval:值写入到这个内存空间
Link with -pthread.
NAME
sem_close - close a named semaphore//关闭不是删除
关闭有名信号量
SYNOPSIS
#include <semaphore.h>
int sem_close(sem_t *sem);
sem:你要对哪个信号量进程进行操作
Link with -pthread.
NAME
sem_unlink - remove a named semaphore//删除一个有名信号量
SYNOPSIS
#include <semaphore.h>
int sem_unlink(const char *name);
name:路径名
Link with -pthread.
NAME
sem_destroy - destroy an unnamed semaphore
销毁一个无名信号量
SYNOPSIS
#include <semaphore.h>
int sem_destroy(sem_t *sem);
sem:你要销毁哪个无名信号量
Link with -pthread.
正式项目里面一旦发现是多个进程/线程对一个资源进行读写,那么我们就要保护
第三十三天(信号量)
肉夹馍不加青椒2025-08-21 13:54
相关推荐
喵手3 分钟前
Java异常处理最佳实践:如何避免捕获到不必要的异常?猿java15 分钟前
精通MySQL却不了解OLAP和 OLTP,正常吗?渣哥28 分钟前
面试官:为什么阿里巴巴要重写HashMap?ConcurrentHashMap哪里不够用?喵手30 分钟前
Java中的HashMap:你了解它的工作原理和最佳实践吗?weixin_4565881534 分钟前
【java面试day16】mysql-覆盖索引心月狐的流火号36 分钟前
计算机I/O模式演进与 Java NIO 直接内存猿究院-赵晨鹤39 分钟前
JVM基础知识总结代码输入中...1 小时前
JVM常见面试题及答案猿究院--冯磊1 小时前
JVM垃圾收集器