第三十三天(信号量)

复制代码
非常非常非常.....的重要

在共享内存的代码里面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,&lt,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,&lt,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.

正式项目里面一旦发现是多个进程/线程对一个资源进行读写,那么我们就要保护
相关推荐
喵手3 分钟前
Java异常处理最佳实践:如何避免捕获到不必要的异常?
java·后端·java ee
猿java15 分钟前
精通MySQL却不了解OLAP和 OLTP,正常吗?
java·后端·面试
渣哥28 分钟前
面试官:为什么阿里巴巴要重写HashMap?ConcurrentHashMap哪里不够用?
java
喵手30 分钟前
Java中的HashMap:你了解它的工作原理和最佳实践吗?
java·后端·java ee
weixin_4565881534 分钟前
【java面试day16】mysql-覆盖索引
java·mysql·面试
心月狐的流火号36 分钟前
计算机I/O模式演进与 Java NIO 直接内存
java·操作系统
猿究院-赵晨鹤39 分钟前
JVM基础知识总结
java·jvm
代码输入中...1 小时前
JVM常见面试题及答案
java·jvm·java面试·java面试题·jvm面试题
猿究院--冯磊1 小时前
JVM垃圾收集器
java·jvm·算法