11月5日笔记并发5_信号量(信号量集的id,PV操作,有名无名信号量,单个信号量[POSIX sem],无名的销毁)

int semget(key_t key, int nsems, int semflg);

int semctl(int semid, int semnum, int cmd, arg);

int semop(int semid, struct sembuf *sops, size_t nsops);

int semtimedop(int semid,  struct sembuf *sops,  size_t nsops, const struct timespec  *timeout);

sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode,  unsigned int value);

int sem_init(sem_t *sem, int pshared, unsigned int value);

int sem_wait(sem_t *sem);

int sem_trywait(sem_t *sem);

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

int sem_post(sem_t *sem);

int sem_getvalue(sem_t *sem,  int *sval);

int sem_close(sem_t *sem);

int sem_unlink(const char *name);

int sem_destroy(sem_t *sem);

                                                                                                                                                           


1.问题
    如果有两个或两个以上的并发的实体(进程/线程)去访问
    同一个共享资源的时候(硬件层面,软件层面),

    必须保证这个共享资源的有序访问 否则会产生不可预期的后果

    分析问题
        出现问题的原因就是共享内存的非法访问

        并发 =>共享资源竞争 =》共享资源的非法访问=》BUGS

        显然 放弃并发不现实 
        保证并发的情况 避免资源的竞争=》访问共享资源这件事情
        所以不能并发 必须串行执行


2.信号量机制
    1.信号量是一个什么东西呢?
    信号量(semaphore) 是一种用于提供不同进程间或一个进程内部
    不同线程间的 同步机制
        进程、线程  并发的实体
        同步 并发的实体间 相互等待 相互制约
        有序的 有条件的访问
    2.信号量的作用
        信号量的作用只有一个 为了保护共享资源
        让共享资源的访问变得 有序
    3.什么需要信号量
        什么时候需要使用?
            有保护对象(有共享资源需要保护的时候) 
            才需要信号量进程保护

            一个保护对象 就需要一个信号量。


3.信号量如何保护共享资源
    保护是指:让这个被保护的对象(共享资源)有序访问  如“互斥”
    共享资源: 大家都访问的资源

    信号量机制 其实是程序员之间的一种约定,用来保护共享资源的
    比如说进程A和进程B都要访问一个互斥设备,那么可以用一个
    信号量来表示现在能不能 访问该设备,然后每一个进程访问 该设备的
    都先去访问信号, 如果可以访问,则先将信号量设置为"NO",再去
    访问互斥设备。访问完了之后 把信号量设置为“YES” 

        (如同高铁上的厕所和车头的显示牌)

    访问信号量的行为是一个 “原子操作”
    原子操作就是 不可被打断 不可以被分隔的操作


    1. 先访问共享资源的信号量 看是否允许
    2. 如果允许访问,则把信号量上锁LOCK   
        如果不允许访问 等到信号量解锁
    3. 访问共享资源的代码区域     "临界区" (显示“YES” “NO” 的区域)
    4. 解锁 把信号量的释放


    信号量的实现
        信号量本质上是一个大家都可以访问的整数
        一个进程或线程可以再某个信号量上执行如下三种操作
        (1)创建(Create)一个信号量: 这要求调用者指定信号量的 初始值
            (同时允许几个并发的实体访问这个资源)

            sem = 1 表示互斥访问 同时只能由一个进程访问所保护的资源
                               这种情况下 这个信号量叫 互斥信号量
            sem = 5 表示允许 同时五个 并发的实体去访问所保护的资源
        
        (2)等待(wait)一个信号
            该操作会测试这个信号量的 值 如果其值 <= 那么会等待(阻塞),
            一旦其值 >0 这个时候 它将信号量 -1,然后继续往下执行临界区的代码
            其函数的大致实现

            while(semaphore <= 0)
            {
                sleep();//wait block
            }
            semaphore--;

            自己这么写是不可以的。以上代码必须是原子操作
            原子操作就是 不可被打断 不可以被分隔的操作

            一旦成功 lock  就可以去执行临界区的代码


            P操作:proberen 尝试 荷兰语
            down
            lock 上锁
        
        (3)释放一个信号量
            该操作将信号量的值+1  其原理类似如下的代码

            semaphore++;//原子操作

            V操作:verhogen 增加
            up
            unlock

    信号量保护目标是通过
        在临界区域前进行 一次P操作
        在临界区域后进行 一次V操作
    来实现


    思考:
        1、在遇到共享资源在不同进程或线程访问时
            考虑避免竞争,
            1.明确谁是 "共享资源",即谁是保护的对象
            2.确定“临界区”:操作共享资源的代码区域 我们称之为临界区
            3.一个保护的对象就需要一个信号量
                P操作
                临界区
                V操作
        
        2.现在有五个资源A,B,C,D,E,程序员决定用一个信号量S来同时保护五个资源
            P(s)
            A
            V(S)

            P(S)
            B
            V(S)
            .....
        -----------------------------------
        请问这种设计有什么问题?
                可以达到保护的目的
                但是降低了并发度,A,B本身是可以同时访问的
        
        3.现在有五个资源A,B,C,D,E,程序员决定用五个个信号量
        s1,s2,s3,s4,s5来分别保护五个资源
         S1->A
         S2->B
         。。。。
        -----------------------------
        如果希望同时访问A B
        P(s1)
        P(s2)
        同时访问AB的临界区
        V(s2)
        v(s1)
        ---------------------
        P(s2)
        P(S1)
        同时访问AB的临界区
        V(S1)
        V(S2)
            这么设计有什么问题?
            deadlock

        如何避免死锁的产生呢?
            如果存在多把锁 那么整个工程的上的上锁顺序和解锁顺序
            必须一样
       (可以去了解一下如何避免死锁的产生)
    


4.linux内核中信号量具体是如何实现的
    System V 信号量
    Posix 信号量
    4.1 System V semaphore
步骤:            ftok
    ---> 获取一个System V IPC 对象的 Key  ---》创建一个System信号量并初始化
    ----》P/V操作

    System V 信号量:
        计数信号量集:(计数信号量数组)

        计数信号量: 该信号量的值可以 >1 ,它所保护的共享资源允许多任务同时访问它
        互斥信号量:   该信号量的值要么是1 要么是0,它所保护的共享资源
                              同一时刻只能由一个任务去访问它

    semget
        NAME
            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_t key: ftok获取的的key
         int nsems: 要创建信号量集中信号量的个数
                            如果不是创建而是打开一个已经存在的信号量集,
                            此处的参数可以为0,一旦创建一个信号量集
                            其信号量的个数就不能改变了
               int semflg :
                        (1)创建
                            IPC_CREAT|权限位    0664
                        (2)打开  0
                    
                    成功 返回信号量集 的 id
                    失败 返回-1 同errno被设置。
    When creating a new semaphore set, semget() initializes the set's  associated
       data structure, semid_ds (see semctl(2)), as follows:


    在创建一个新的信号量集时
    信号量集中的信号量的值是不确定 所以应该立刻初始化它们   

(2)semctl 控制操作(设置或获取信号量集中某个或某些信号量的值)

    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, arg);

          int semid:  semget()的 返回值 指向量集的 id
          int semnum: 要操作信号量集中的那个信号量,就是信号量数组的 下标
                                数组下标从0开始
            
       int cmd:命令号,常用的有

                GETVAL:获取 第semnum个信号量的值
                SETVAL:设置 第semnum个信号量的值
                GETALL:获取 这个信号量集 所有信号量的值
                SETALL:设置 这个信号量集 所有信号量的值
                IPC_RMID:删除这个信号量集
                ....

            arg:  针对不同的命令 第四个参数不一样
                cmd == IPC_RMID 第四个参数可以不要
                    semctl(semid,0,IPC_RMID);
                cmd == GETVAL   第四个参数也不要
                             其函数返回值的含义为
                        该信号量的值
                    如
                        int val = semctl(semid,0,GETVAL);
                        val等于信号量集中第一个信号量的值
                cmd == SETVAL 那么第四个参数的类型应该为  int
                              表示要设置这个信号量的值
                        如
                            int sem_val = 1;
                            int r = semctl(semid,1,SETVAL,semval);
                            将信号量集semid中的第二个信号量设置为semval;
                cmd == GETALL  第四个参数 应该接收信号量集的值 类型为 unsigned short vals[]
                        unsigned short vals[10];
                        int r = semctl(semid,0,GETALL,vals);
                        //vals[0] 第1个信号量的值
                        //vals[1] 第2个信号量的值
                        ......
                cmd == SETALL 第四个参数 应该设置信号量集的每个信号量的值

                                                                类型为 unsigned short vals[]
                        unsigned short vals[10] = {1,1,1,1,1,1,1,1,1,1};
                        int r = semctl(semid,0,SETALL,vals);
                        //第1个信号量的值 设置为了 vals[0]
                        //第2个信号量的值 设置为了 vals[1]
                        ........
            返回值
                根据命令不同 semctl 的返回值的含义不一样

                    0 ——》成功
                    -1 -》失败
    
    (3)semop:Symtem V 信号量P/V操作

    NAME
        semop, semtimedop - System V semaphore operations

    SYNOPSIS
        #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/sem.h>
        在System V 信号量中 对信号量进行 P/V 操作 用一个结构体 struct sembuf 来描述
            struct sembuf
            {
                unsigned short sem_num;   /* semaphore number */
                                //要进行PV操作的信号量,它在信号量集中的下标
                short          sem_op;          /* semaphore operation */
                                                > 0  =>V操作

                                                = 0  try to try  看一下这个信号量是否有人用

                                                < 0 =》P操作

                                semval(信号量的值) = 原semval + sem_op
                short          sem_flg;       /* operation flags */
                                                0 默认  如果P操作获取不了信号量,就会阻塞
                                
                                                IPC_NOWAIT
                                                    如果能获取则获取,不能就走人(返回-1)

                                                SEM_UNDO :  撤销!!!!
                                                    防止信号量的带锁退出
                                                    如果设置了 SEM_UNDO这个标志 内核会额外记录
                                                    该进程对该信号的所有P/V操作然后,在该进程退出时
                                                    还原对该信号量的操作
                                如果
                                    p   v   p   v    kill
                                    -1  +1  -1  +1   0
                                
                                    P   V   P   KILL
                                    -1  +1  -1  -1 (还原+1,保证对信号量的操作为0)

            }


            struct sembuf  表示对 一个信号量 的P/V操作
            如果对两个信号量 就需要对应两个struct sembuf
            如果对多个信号量 就需要对应多个struct sembuf

        int semop(int semid, struct sembuf *sops, size_t nsops);

               int semid:信号量集的ID
     struct sembuf *sops: struct sembuf的数据
                    struct sembuf [0]  表示对第0个信号量的P/V操作;

      size_t nsops: 第二个参数元素的个数

                成功 返回0 
                失败 返回-1 同时errno被设置


        semop 可能会阻塞当前的线程/进程,如果是P操作,获取不了的时候
            且 IPCNOWAIT 没有设置 它就会一直等 直到能获取


        semtimedop 限时等待
        第四个参数time描述 “限时”:如果这段时间内没有等到,也返回 失败

        int semtimedop(int semid,  struct sembuf *sops,  size_t nsops,
                                  const struct timespec  *timeout);
            struct timespec
            {
                long tv_sec; //秒数
                long tv_nsec //纳秒
                    1s == 1000ms
                    1ms == 1000us
                    1us == 1000ns
            }


    4.2 POSIX semaphore ->单个信号量
    有名信号量   可以用于进程或线程间 同步、互斥
                         有名字只是在文件系统中有个入口(文件名,inode)
                         信号量的对象是 保存在内核中
                         既可以用于任意进程间,也可以用于线程间同步
                
     无名信号量

                没有名字 无名信号量 存在与内存中

                如果这段内存在一个 "内核的共享内存中"
                进程和线程都可以进程访问 =》用于进程/线程间 同步 和 互斥

                如果这段内存在以 进程 地址空间,只能用于该
                进程内部所有线程的同步/互斥

    (1)创建一个POSIX信号量
        POSIX 信号量都是用类型 sem_t来表示 不论是有名还是无名信号量       

        (1)有名信号量

        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);

          const char *name:文件名 要创建或打开的POSIX有名信号量在文件系统中的路径
                            路径名要求 以'/' 开头且只能有一个'/'   你的文件只能创建根目录下面
                            e.g
                                "/test.sem"
             int oflag :
                        (1) 打开  0
                        (2) 创建 O_CREAT

                    当创建一个有名信号量时 才需要指定第三个和第四个参数,此时
                    系统自动匹配第二个函数去调用

       mode_t mode:创建的权限 有两种方式指定
                        (1) S_IRUSR....   (宏定义法)
                        (2) 0664               (八进制法)
      unsigned int value : 指定创建的有名信号量的初值

                返回值
                    成功 返回一个 sem_t的指针  指向一个POSIX有名信号量
                    失败 返回一个 SEM_FAILED, 同时errno被设置

            Link with -pthread. POSIX是在线程这个库(pthread)中实现的
            所以编译的时候 加上 -pthread

        (2)无名信号量
            (1)定义或分配一个无名信号量sem_t
                sem_t t1;
                sem_t *psem = malloc(sizeof(sem_t));

                这两种方式定义的信号量在进程的 地址 里面
                只能用于该进程下 线程的同步/互斥
                进程间 使用有名信号量   如果一定要使用无名 则共享内存
            (2) 初始化无名信号量 
                sem_init();

                NAME
                    sem_init - initialize an unnamed semaphore

                SYNOPSIS
                    #include <semaphore.h>

                    int sem_init(sem_t *sem, int pshared, unsigned int value);

           sem_t *sem:指向要 初始化 的 无名信号量!! 有名信号量的sem_open不要往这里塞
            int pshared:  该无名信号量的共享方式
                            0  进程内部的线程共享
                            1  不同进程间共享
                                    sem指向的值应该时共享内存区域
      unsigned int value: 指定初始化无名信号量的初值

                        成功 返回 0 
                        失败 返回-1 同时errno被设置
                    Link with -pthread.       


                 (3)POSIX信号量 P/V操作

        NAME
            sem_wait, sem_timedwait, sem_trywait - lock a semaphore

        SYNOPSIS
            #include <semaphore.h>
                “死等” 只要拿不到访问权 就一直等
            int sem_wait(sem_t *sem);

         sem_t *sem:创建 或 初始化得到的信号量
                返回值
                    成功 返回 0
                    失败 返回 -1 同时errno被设置

                非阻塞版本 试一下
                如果能够获取 则获取 不能则返回-1 不等待

            int sem_trywait(sem_t *sem);

         sem_t *sem:创建 或初始化得到的信号量
                返回值
                    成功 返回 0
                    失败 返回 -1 同时errno被设置

                会等待 超时 没有获取到访问权就放弃了 返回-1

            int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);


      sem_t *sem:创建或初始化得到的信号量
  const struct timespec *abs_timeout: 是一个绝对时间
                    abs_timeout 指定等待的绝对时间
                    绝对时间 = 当前绝对时间+相对时间
                    如果希望等待1s  我需要先获取当前绝对时间 再+1秒进行设置

       

             e.g
                        假设要延迟1.5s
                        struct timespec ts;
                        clock_gettime(CLOCK_REALTIME,&ts);//获取当前的绝对时间
                        ts.tv_sec += 1;
                        ts.tv_nsec += 50000000;
                        if(ts.tv_nsec > 1000000000)//如果你的纳秒数大于10亿
                        {
                            ts.tv_sec ++;
                            ts.tv_nsec -= 1000000000;
                        }
                sem_timedwait(&sem,&ts);

                返回值
                    成功 返回 0
                    失败 返回 -1 同时errno被设置


            Link with -pthread.       

              V操作

        NAME
            sem_post - unlock a semaphore

        SYNOPSIS
            #include <semaphore.h>

            int sem_post(sem_t *sem);

        sem_t *sem: 要进行V操作那个信号量

                返回
                    成功 返回会0
                    失败 返回-1 同时errno被设置

            Link with -pthread.


    (4)POSIX信号量其他操作
        1. 获取某个POSIX信号量的值

        NAME
            sem_getvalue - get the value of a semaphore

        SYNOPSIS
            #include <semaphore.h>

            int sem_getvalue(sem_t *sem,  int *sval);

             sem_t *sem: 要获取那个信号量的值
              int *sval: int *指向的空间  用来保存获取到的信号量的 值
                返回
                    成功 返回会0
                    失败 返回-1 同时errno被设置

            Link with -pthread.

        2.1有名信号量的关闭和删除操作
            sem_close:  关闭一个POSIX有名信号量
            sem_unlink: 删除一个POSIX有名信号量            

            SYNOPSIS
                #include <semaphore.h>

                int sem_close(sem_t *sem);

        sem_t *sem:要关闭的有名信号量


                    返回
                    成功 返回会0
                    失败 返回-1 同时errno被设置             

                   Link with -pthread.

            NAME
                sem_unlink - remove a named semaphore

            SYNOPSIS
                #include <semaphore.h>

                int sem_unlink(const char *name);

     const char *name:要删除的有名信号量的文件名 路径的规则同打开一样

                    返回
                    成功 返回会0
                    失败 返回-1 同时errno被设置

                Link with -pthread.

        2.2无名的销毁操作

            NAME
                sem_destroy - destroy an unnamed semaphore

            SYNOPSIS
                #include <semaphore.h>

                int sem_destroy(sem_t *sem);

                Link with -pthread.

练习:
    1.信号量一般是用于保护共享资源,但其实信号还有一些
    其他的作用 比如 让 fork完之后让子进程先执行

    




                


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值