非常非常非常.....的重要在共享内存的代码里面p1.c实质是有问题lt._flag = 1;//这里先置1if(c == 'Q')sprintf(lt._buf,"quit");elsesprintf(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的时间段里面前面那个时间就会将其置1p2看到变成1了,它就可以去读后面内容了,p2很有可能拿到了一个错误的内容现在我要解决这个问题lt._flag = 0;//if(c == 'Q')sprintf(lt._buf,"quit");elsesprintf(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操作就是++flagflag = 1 ->这种信号量我们叫互斥信号量 但是有的时候某一些共享资源支持多个进程同时访问,那么这个时候flag > 1P操作一般我们认为是 --就可以了,但是有的时候可能一个进程需要多个资源,这个时候就不是--,它需要 -= nV操作有的时候就需要 += 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操作的进程idint semncnt;//使其增长的进程数量int semzcnt;//使其变成0的进程数量
};创建一个信号量集semget - get a System V semaphore set identifierSYNOPSIS#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.新创建出来的信号量集里面的信号量的值是不确定的所以我们需要给这些信号量马上设置值NAMEsemctl - 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:信号量集的idsemnum:信号量集里面有多个信号量,你现在要操作哪一个你是不是要给我指明从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);//设置信号量集里面的第二个信号量的值为4cmd == 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_INFOstruct 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)NAMEsemop, semtimedop - System V semaphore operationsSYNOPSIS#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 -> 将信号量的值 += 3short 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这个函数如果是阻塞上锁,别人没有释放锁的时候,它是上不上去的,这个时候就会卡在semopsemtimedop给了一个时间,这个时间到了就不等了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 -> 2123456 -> 1456123 -> 1412356 -> 0451236 -> 0POSIX semaphore -> 单个信号量有名信号量:内容在内核里面,在文件系统里面有一个名字因此可以用于不同的进程间或者线程间无名信号量:没有名字,因此只能通过遗传,因此只能用于有亲缘关系的进程间如果这个无名信号量存在于父进程的内存空间,当子进程拷贝过去之后,父子里面的信号量就变成两个了这个时候父进程就会操作父进程,子进程操作的就是子进程的,这样就起不到保护的作用了那么我们就需要让这个信号存在于共享内存里面 --- 这种操作明显很麻烦,不建议使用或者线程间有名信号量:需要创建或者打开
NAMEsem_open - initialize and open a named semaphoreSYNOPSIS#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:你创建的时候需要这个权限 0664value:创建的时候才会有值的设置,如果是打开一个信号量是没有值的设置的只有创建成功才会被设置进去返回值:成功返回一个sem_t指针,指向我们的信号量失败返回SEM_FAILED,同时errno被设置Link with -pthread.//链接这个库 pthread 因此编译的时候需要在后面加上 -lpthread无名信号量只能初始化
NAMEsem_init - initialize an unnamed semaphoreSYNOPSIS#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);sem:保存我们的信号量pshared:共享方式0 :进程的内部线程共享1 : 不同进程间的共享 ---- 这种不建议使用value:值成功0 失败-1Link with -pthread.P操作
NAMEsem_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操作
NAMEsem_post - unlock a semaphore 解锁SYNOPSIS#include <semaphore.h>int sem_post(sem_t *sem);sem:你要对哪个信号量进程P操作Link with -pthread.POSIX信号量的其它操作NAMEsem_getvalue - get the value of a semaphore获取信号量的值
SYNOPSIS#include <semaphore.h>int sem_getvalue(sem_t *sem, int *sval);sem:你要对哪个信号量进程进行操作sval:值写入到这个内存空间Link with -pthread.NAMEsem_close - close a named semaphore//关闭不是删除关闭有名信号量
SYNOPSIS#include <semaphore.h>int sem_close(sem_t *sem);sem:你要对哪个信号量进程进行操作Link with -pthread.NAMEsem_unlink - remove a named semaphore//删除一个有名信号量SYNOPSIS#include <semaphore.h>int sem_unlink(const char *name);name:路径名Link with -pthread.NAMEsem_destroy - destroy an unnamed semaphore销毁一个无名信号量
SYNOPSIS#include <semaphore.h>int sem_destroy(sem_t *sem);sem:你要销毁哪个无名信号量Link with -pthread.正式项目里面一旦发现是多个进程/线程对一个资源进行读写,那么我们就要保护