Linux C/C++ 历程控制

打印 上一主题 下一主题

主题 995|帖子 995|积分 2985

1.信号

​ 信号(signal)是软件中断,是历程之间相互传递消息的一种方法,用于关照历程发生了变乱,但
是,不能给历程传递任何数据。
​ 信号产生的缘故原由有很多,在 Shell 中,可以用 kill 和 killall 命令发送信号:
  1. ki -信号的类型 进程编号
  2. killall -信号的类型 进程名
复制代码
如果命令无效,需安装psmisc
  1. sudo yum install psmisc
复制代码
信号的处置惩罚:


  • 对该信号的处置惩罚采取系统的默认操作,大部门的信号的默认操作是终止历程
  • 设置信号的处置惩罚函数,收到信号后,由该函数来处置惩罚
  • 忽略某个信号,对该信号不做任何处置惩罚,就像未发生过一样
signal()函数可以设置步调对信号的处置惩罚方式
  1. 函数声明:
  2.         sighandler_t signal(int signum,sighandler_t handler);
  3.        
  4. signum        //信号编号
  5. handler        //信号的处理方式,有三种
  6. 1)SIG_DFL:恢复参数signum所指信号处理方法
  7. 2)一个自定义处理信号的函数,函数的形参是信号的编号
  8. 3)SIG_IGN:忽略参数signum所指信号
复制代码
  1. #include<iostream>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. using namespace std;
  5. void func(int signum)
  6. {
  7.     cout<<"signals"<<endl;
  8. }
  9. int main()
  10. {
  11.     signal(1,func);//注册回调函数func()
  12.     signal(15,func);
  13.     signal(2,SIG_IGN);//忽略2信号
  14.     signal(15,SIG_DFL);//恢复15信号
  15.    
  16.     alarm(5);                //5s定时发送14信号
  17.    
  18.     while(true)
  19.     {
  20.         cout<<1<<endl;
  21.         sleep(1);
  22.     }
  23. }
复制代码
应用场合:
​ 服务步调运行在后台,如果想让中止它,杀掉不是个好办法,因为历程被杀的时间,是突然殒命没有安排善后工作。
​ 如果向服务步调发送一个信号,服务步调收到这个信号后,调用一个函数,在函数中编写善后的代码,步调就可以有筹划的退出。
​ 向服务步调发送0的信号,可以检测步调是否存活。
  1. [root@localhost 06demoerror]# killall -0 demo_error
  2. demo_error: no process found
复制代码
  1. #include<iostream>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. using namespace std;
  5. void EXIT(int signum)
  6. {
  7.         cout<<"程序退出\n"<<endl;
  8.         exit(0);
  9. }
  10. int main(int argc,cgar *argv[])
  11. {
  12.         //忽略所有信号,防止进程被信号异常终止
  13.         for(int ii = 1;ii <= 64;ii++) signal(ii,SIG_IGN);
  14.        
  15.         //收到2和15信号(Ctrl+C和kill、killall),程序退出
  16.         signal(2,EXIT);
  17.         signal(15,EXIT);
  18.        
  19.         while(true)
  20.         {
  21.                 cout<<"执行一次任务"<<endl;
  22.                 sleep(1);
  23.         }
  24. }
复制代码
2.历程终止

有八种方式可以终止历程,此中5中为正常终止
1)在main()函数中用return返回
2)在恣意函数中调用exit()
3)在恣意函数中调用_exit()或 _Exit()函数
4)在最后一个线程从其他启动例程(线程主函数)用return返回
5)在最后一个线程中调用pthread_exit()返回
非常终止有三种方式
6)调用abort()函数终止
7)吸取到一个信号
8)最后一给线程对取消请求作出响应
在main()函数中,return返回值即终止状态,如果没有return或调用exit(),历程终止状态为0
在Shell中检察简称终止状态
  1. [root@localhost 06demoerror]# ./demo
  2. [root@localhost 06demoerror]# echo $?
  3. 0
复制代码
正常终止历程的三个函数
  1. void exit(int status);
  2. void _exit(int status);
  3. void _Exit(int status);                //status为进程终止状态
复制代码
  如果历程被非常终止,终止状态为 非0
    用于服务步调的调度、日志和监控
  资源释放的标题:


  • return 体现函数返回,会调用局部对象的析构函数,main()函数中的 return 还会调用全局对象的析构函数。
  • exit()体现终止历程,不会调用局部对象的析构函数,只调用全局对象的析构函数。
  • exit()会实验清理工作,然后退出,exit()和 Exit()直接退出,不会实验清理工作。
历程的终止函数:atexit()登记终止函数(最多32个),这些函数exit()自动调用
  1. int atexit(void (*function)(void));
复制代码
  exit()调用终止函数的次序与登记时相反
  3.调用可实验步调

Linux提供了system()函数和exec函数族,在C++步调中,可以实验其他步调(二进制文件、操作系统命令或者Shell脚本)
system()函数
  1. #include<stdlib.h>
  2. int system(const char * string);
  3. 执行程序不存在,返回非0
  4. 执行成功,程序终止状态是0,system()函数返回0
  5. 执行成功,程序终止状态不是0,system()函数返回非0
复制代码
exec()函数族
exec函数族提供了另一种在历程中调用步调(二进制文件或Shell脚本)的方法
  1. int execl(const char *path,const char *arg,…);
  2. int execlp(const char *file,const char *arg,…);
  3. int execle(const char *path,const char *arg,…,char *const envp[]);
  4. int execv(const char *path,char *const argv[]);
  5. int execvp(const char *file,char *const argv[]);
  6. int execvpe(const char *file,char *const argv[],char *const envp[]);
复制代码
  注意:
  1)如果实验失败则直接返回-1,失败缘故原由存于errno中
  2)新历程的历程编号和原历程雷同,但是新历程代替了原历程的代码段、数据段和堆栈
  3)如果实验乐成则函数不会返回,当在主步调乐成调用了exec后,被调用的步调将代替调用者步调,也就是说exec函数之后的代码都不会被实验
  4)在实际开发中,最常用execl()和execv()
  
4.历程创建

整个Linux系统全部的历程是一个树形结构


  • 0号历程是所有历程的祖先,他创建了1号和2号历程
  • 1号历程(systemed)负责实验内核的初始化工作和举行系统设置
  • 2号历程(kthreadd)负责所有内核线程的调度和管理
    1. yum -y install psmisc
    2. pstree -p 进程编号        //查看进程树
    复制代码
每个历程都有一个非负整数体现的唯一历程ID。固然是唯一的,但是历程ID可以复用。当一个历程终止后,其历程ID就成了复用的候选者。Linux采取耽误服用算法,让新建历程的ID不同于迩来终止的历程所使用的ID。这样防止了新历程被误认为是使用了同一ID的某个已终止历程。
  1. pid_t getpid(void)        //获取当前进程ID
  2. pid_t getppid(void)        //获取父进程ID
复制代码
fork()函数
一个现有的历程可以调用fork()函数创建一个新历程
  1. pid_t fork(void);
复制代码
fork()函数被调用一次,但返回两次。两次返回的区别是子历程返回值是0,而父历程返回值是子历程的历程ID。
子历程和父历程继承实验fork()之后的代码,子历程是父历程的副本。
子历程得到了父历程数据空间、堆和栈的副本。
   子历程拥有的是副本,不是共享
  fork()之后,父历程和子历程的实验次序是不确定的。
fork()两种用法:

  • 父历程复制自己,然后,父历程和子历程分别实验不同的代码。这种用法在网络服务步调中很常见,父历程期待客户端的毗连请求,当请求到达时,父历程调用fork(),让子历程处置惩罚些请求,而父历程则继承期待下一个毗连请求。
  • 历程要实验另一个步调。这种用法在 Shell 中很常见,子历程从 fork()返回后立即调用 exec。
共享文件:fork()的一个特性是在父历程中打开的文件形貌符都会被复制到子历程中,父历程和子历程共享同一个文件偏移量。
   如果父历程和子历程写同一文件形貌符指向的文件,但又没有任何同步形式,那么他们的输出可能会相互混合。
  vfork()函数
vfork()函数的调用和返回值与 fork()雷同,但两者的语义不同。
vfork()函数用于创建一个新历程,而该新历程的目标是 exec 一个新步调,它不复制父历程的所在空间,因为子历程会立即调用 exec,于是也就不会使用父历程的所在空间。如果子历程使用了父历程的所在空间,可能会带来未知的结果。
vfork()和fork()的另一个区别是:vfork()包管子历程先运行,在子历程调用exec或exit()之后,父历程才规复运行

5.僵尸历程

如果父历程比子历程先退出,子历程将被1号历程托管(这也是一种让步调在后台运行的方法)。
如果子历程比父历程先退出,而父历程没有处置惩罚子历程退出的信息,那么,子历程将成为僵尸进
程。
僵尸历程有什么危害?
内核为每个子历程保留了一个数据结构,包罗历程编号、终止状态、使用CPU 时间等。父历程如果处置惩罚了子历程退出的信息,内核就会释放这个数据结构,父历程如果没有处置惩罚子历程退出的信息,内核就不会释放这个数据结构,子历程的历程编号将一直被占用。系统可用的历程编号是有限的,如果产生了大量的僵尸历程,将因为没有可用的历程编号而导致系统不能产生新的历程。
僵尸历程的制止:
1)子历程退出的时间,内核会向父历程发头 SIGCHLD 信号,如果父历程用 signal(SIGCHLD,SIGGN)关照内核,体现自己对子历程的退出不感兴趣,那么子历程退出后会立即释放数据结构。
2)父历程通过 wait()/waitpid()等函数期待子历程结束,在子历程退出之前,父历程将阻塞期待。
  1. pid_t wait(int *stat_loc);
  2. pid_t waitpid(pid_t pid, int *stat_loc, int options);
  3. pid_t wait3(int *status, int options, struct rusage *rusage);
  4. pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
复制代码
返回值是子历程的编号
stat_loc 是子历程终止的信息:
a)如果是正常终止,宏 WIFEXITED(stat loc)返回真,宏 WEXITSTATUS(stat_loc)可获取终止状态;
b)如果是非常终止,宏 WTERMSIG(stat loc)可获取终止历程的信号
3)如果父历程很忙,可以捕捉 SIGCHLD 信号,在信号处置惩罚函数中调用 wait()/waitpid()。

6.多历程与信号

Linux 操作系统提供了 kill和 killall 命令向历程发送信号,在步调中,可以用kill()函数向别的历程发送信号。
函数声明:
  1. int kill(pid t pid, int sig);
复制代码
kil()函数将参数 sig 指定的信号给参数 pid 指定的历程。
参数 pid 有几种情况:
1)pid>0 将信号传给历程号为 pid 的历程。
2)pid=0 将信号传给和如今历程雷同历程组的所有历程,常用于历程给子历程发送信号,注意,发送信号者历程也会收到自己发出的信号。
3)pid=-1 将信号广播传送给系统内所有的历程,比方系统关机时,会向所有的登录窗口广播关机信息。
sig:准备发送的信号代码,如果其值为0则没有任何信号送出,但是系统会实验错误查抄,通常会利用 sig 值为零来检验某个历程是否仍在运行。
返回值说明: 乐成实验时,返回0;失败返回-1,errno 被设置。
8.共享内存

多线程共享历程的所在空间,如果多个线程必要访问同一块内存,用全局变量就可以了。
在多历程中,每个历程的所在空间是独立的,不共享的,如果多个历程必要访问同一块内存,不能用全局变量,只能用共享内存。“
共享内存(Shared Memory)答应多个历程(不要求历程之间有血缘关系)访问同一个内存空间是多个历程之间共享和传递数据最高效的方式。历程可以将共享内存毗连到它们自己的所在空间中,如果某个历程修改了共享内存中的数据,别的的历程读到的数据也将会改变。
共享内存并未提供锁机制,也就是说,在某一个历程对共享内存的举行读/写的时间,不会制止别的历程对它的读/写。如果要对共享内存的读/写加锁,可以使用信号量。
Linux 中提供了一组函数用于操作共享内存。
shmget函数
该函数用于创建/获取共享内存。
  1. int shmget(key_t key, size_t size, int shmflg);
复制代码
key 共享内存的键值,是一个整数(typedef unsigned int key_t),一样平常采取十六进制,比方0x5005,不同共享内存的 key 不能雷同。
size 待创建的共享内存的巨细,以字节为单位。
shmfg 共享内存的访问权限,与文件的权限一样,比方 0666|IPC_CREAT,体现全部用户对它可读写,IPC_CREAT体现如果共享内存不存在,就创建它。
返回值:乐成返回共享内存的id(一个大于0的整数),失败返回-1
  1. ipcs -m        //查看系统的共享内存
  2. ipcrm -m 共享内存id        //手工删除共享内存
复制代码
shmat 函数
该函数用于把共享内存毗连到当前历程的所在空间。
  1. void *shmat(int shmid, const void *shmaddr, int shmflg);
复制代码
shmid 由 shmget()函数返回的共享内存标识。
shmaddr 指定共享内存毗连到当前历程中的所在位置,通常填0,体现让系统来选择共享内存的
所在。
shmflg 标记位,通常填 0。
调用乐成时返回共享内存起始所在,失败返回(void*)-1
shmdt 函数
该函数用于将共享内存从当前历程中分离,相称于shmat()函数的反操作。
  1. int shmdt(const void *shmaddr);
复制代码
shmaddr shmat()函数返回的所在。
调用乐成时返回 0,失败时返回-1。
shmctl 函数
该函数用于操作共享内存,最常用的操作是删除共享内存。
int shmctl(int shmid, int command, struct shmid ds *buf);
shmid shmget()函数返回的共享内存 id。
command操作共享内存的指令,如果要删除共享内存,填IPC RMID。
buf 操作共享内存的数据结构的所在,如果要删除共享内存,填 0。
调用乐成时返回0,失败时返回-1。
   注意,用root 创建的共享内存,不管创建的权限是什么,普通用户无法删除。
  
9.循环队列



  • 共享内存不能自动扩展,只能采取C++内置的数据类型
  • 共享内存不能采取STL容器,也不能使用移动语义
  • 如果要实现多历程的生产/消费者模型,只能采取循环队列
  1. //_public.h
  2. #ifndef __PUBLIC_HH
  3. #define __PUBLIC_HH 1
  4. #include <iostream>
  5. #include <cstdio>
  6. #include <cstdlib>
  7. #include <cstring>
  8. #include <unistd.h>
  9. #include <sys/ipc.h>
  10. #include <sys/shm.h>
  11. #include <sys/types.h>
  12. #include <sys/sem.h>
  13. using namespace std;
  14. // 循环队列。
  15. template <class TT, int MaxLength>
  16. class squeue
  17. {
  18. private:
  19.   bool m_inited;              // 队列被初始化标志,true-已初始化;false-未初始化。
  20.   TT   m_data[MaxLength];     // 用数组存储循环队列中的元素。
  21.   int  m_head;                // 队列的头指针。
  22.   int  m_tail;                // 队列的尾指针,指向队尾元素。
  23.   int  m_length;              // 队列的实际长度。   
  24.   squeue(const squeue &) = delete;             // 禁用拷贝构造函数。
  25.   squeue &operator=(const squeue &) = delete;  // 禁用赋值函数。
  26. public:
  27.   squeue() { init(); }  // 构造函数。
  28.   // 循环队列的初始化操作。
  29.   // 注意:如果用于共享内存的队列,不会调用构造函数,必须调用此函数初始化。
  30.   void init()  
  31.   {
  32.     if (m_inited!=true)      // 循环队列的初始化只能执行一次。
  33.     {
  34.       m_head=0;              // 头指针。
  35.       m_tail=MaxLength-1;    // 为了方便写代码,初始化时,尾指针指向队列的最后一个位置。
  36.       m_length=0;            // 队列的实际长度。
  37.       memset(m_data,0,sizeof(m_data));  // 数组元素清零。
  38.       m_inited=true;
  39.     }
  40.   }
  41.   // 元素入队,返回值:false-失败;true-成功。
  42.   bool push(const TT &ee)
  43.   {
  44.     if (full() == true)
  45.     {
  46.       cout << "循环队列已满,入队失败。\n"; return false;
  47.     }
  48.     // 先移动队尾指针,然后再拷贝数据。
  49.     m_tail=(m_tail+1)%MaxLength;  // 队尾指针后移。
  50.     m_data[m_tail]=ee;
  51.     m_length++;   
  52.     return true;
  53.   }
  54.   // 求循环队列的长度,返回值:>=0-队列中元素的个数。
  55.   int  size()                  
  56.   {
  57.     return m_length;   
  58.   }
  59.   // 判断循环队列是否为空,返回值:true-空,false-非空。
  60.   bool empty()                    
  61.   {
  62.     if (m_length == 0) return true;   
  63.     return false;
  64.   }
  65.   // 判断循环队列是否已满,返回值:true-已满,false-未满。
  66.   bool full()
  67.   {
  68.     if (m_length == MaxLength) return true;   
  69.     return false;
  70.   }
  71.   // 查看队头元素的值,元素不出队。
  72.   TT& front()
  73.   {
  74.     return m_data[m_head];
  75.   }
  76.   // 元素出队,返回值:false-失败;true-成功。
  77.   bool pop()
  78.   {
  79.     if (empty() == true) return false;
  80.     m_head=(m_head+1)%MaxLength;  // 队列头指针后移。
  81.     m_length--;   
  82.     return true;
  83.   }
  84.   // 显示循环队列中全部的元素。
  85.   // 这是一个临时的用于调试的函数,队列中元素的数据类型支持cout输出才可用。
  86.   void printqueue()                    
  87.   {
  88.     for (int ii = 0; ii < size(); ii++)
  89.     {
  90.       cout << "m_data[" << (m_head+ii)%MaxLength << "],value=" \
  91.            << m_data[(m_head+ii)%MaxLength] << endl;
  92.     }
  93.   }
  94. };
  95. // 信号量。
  96. class csemp
  97. {
  98. private:
  99.   union semun  // 用于信号量操作的共同体。
  100.   {
  101.     int val;
  102.     struct semid_ds *buf;
  103.     unsigned short  *arry;
  104.   };
  105.   int   m_semid;         // 信号量id(描述符)。
  106.   // 如果把sem_flg设置为SEM_UNDO,操作系统将跟踪进程对信号量的修改情况,
  107.   // 在全部修改过信号量的进程(正常或异常)终止后,操作系统将把信号量恢复为初始值。
  108.   // 如果信号量用于互斥锁,设置为SEM_UNDO。
  109.   // 如果信号量用于生产消费者模型,设置为0。
  110.   short m_sem_flg;
  111.   csemp(const csemp &) = delete;             // 禁用拷贝构造函数。
  112.   csemp &operator=(const csemp &) = delete;  // 禁用赋值函数。
  113. public:
  114.   csemp():m_semid(-1){}
  115.   // 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
  116.   // 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。
  117.   // 如果用于生产消费者模型,value填0,sem_flg填0。
  118.   bool init(key_t key,unsigned short value=1,short sem_flg=SEM_UNDO);
  119.   bool wait(short value=-1);// 信号量的P操作,如果信号量的值是0,将阻塞等待,直到信号量的值大于0。
  120.   bool post(short value=1); // 信号量的V操作。
  121.   int  getvalue();           // 获取信号量的值,成功返回信号量的值,失败返回-1。
  122.   bool destroy();            // 销毁信号量。
  123. ~csemp();
  124. };
  125. #endif
复制代码
  1. //_public.cpp
  2. #include "_public.h"
  3. // 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
  4. // 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。
  5. // 如果用于生产消费者模型,value填0,sem_flg填0。
  6. bool csemp::init(key_t key,unsigned short value,short sem_flg)
  7. {
  8.   if (m_semid!=-1) return false; // 如果已经初始化了,不必再次初始化。
  9.   m_sem_flg=sem_flg;
  10.   // 信号量的初始化不能直接用semget(key,1,0666|IPC_CREAT)
  11.   // 因为信号量创建后,初始值是0,如果用于互斥锁,需要把它的初始值设置为1,
  12.   // 而获取信号量则不需要设置初始值,所以,创建信号量和获取信号量的流程不同。
  13.   // 信号量的初始化分三个步骤:
  14.   // 1)获取信号量,如果成功,函数返回。
  15.   // 2)如果失败,则创建信号量。
  16.   // 3) 设置信号量的初始值。
  17.   // 获取信号量。
  18.   if ( (m_semid=semget(key,1,0666)) == -1)
  19.   {
  20.     // 如果信号量不存在,创建它。
  21.     if (errno==ENOENT)
  22.     {
  23.       // 用IPC_EXCL标志确保只有一个进程创建并初始化信号量,其它进程只能获取。
  24.       if ( (m_semid=semget(key,1,0666|IPC_CREAT|IPC_EXCL)) == -1)
  25.       {
  26.         if (errno==EEXIST) // 如果错误代码是信号量已存在,则再次获取信号量。
  27.         {
  28.           if ( (m_semid=semget(key,1,0666)) == -1)
  29.           {
  30.             perror("init 1 semget()"); return false;
  31.           }
  32.           return true;
  33.         }
  34.         else  // 如果是其它错误,返回失败。
  35.         {
  36.           perror("init 2 semget()"); return false;
  37.         }
  38.       }
  39.       // 信号量创建成功后,还需要把它初始化成value。
  40.       union semun sem_union;
  41.       sem_union.val = value;   // 设置信号量的初始值。
  42.       if (semctl(m_semid,0,SETVAL,sem_union) <  0)
  43.       {
  44.         perror("init semctl()"); return false;
  45.       }
  46.     }
  47.     else
  48.     { perror("init 3 semget()"); return false; }
  49.   }
  50.   return true;
  51. }
  52. // 信号量的P操作(把信号量的值减value),如果信号量的值是0,将阻塞等待,直到信号量的值大于0。
  53. bool csemp::wait(short value)
  54. {
  55.   if (m_semid==-1) return false;
  56.   struct sembuf sem_b;
  57.   sem_b.sem_num = 0;      // 信号量编号,0代表第一个信号量。
  58.   sem_b.sem_op = value;   // P操作的value必须小于0。
  59.   sem_b.sem_flg = m_sem_flg;
  60.   if (semop(m_semid,&sem_b,1) == -1) { perror("p semop()"); return false; }
  61.   return true;
  62. }
  63. // 信号量的V操作(把信号量的值减value)。
  64. bool csemp::post(short value)
  65. {
  66.   if (m_semid==-1) return false;
  67.   struct sembuf sem_b;
  68.   sem_b.sem_num = 0;     // 信号量编号,0代表第一个信号量。
  69.   sem_b.sem_op = value;  // V操作的value必须大于0。
  70.   sem_b.sem_flg = m_sem_flg;
  71.   if (semop(m_semid,&sem_b,1) == -1) { perror("V semop()"); return false; }
  72.   return true;
  73. }
  74. // 获取信号量的值,成功返回信号量的值,失败返回-1。
  75. int csemp::getvalue()
  76. {
  77.   return semctl(m_semid,0,GETVAL);
  78. }
  79. // 销毁信号量。
  80. bool csemp::destroy()
  81. {
  82.   if (m_semid==-1) return false;
  83.   if (semctl(m_semid,0,IPC_RMID) == -1) { perror("destroy semctl()"); return false; }
  84.   return true;
  85. }
  86. csemp::~csemp()
  87. {
  88. }
复制代码
测试步调1
  1. // demo1.cpp,本程序演示循环队列的使用。
  2. #include "_public.h"
  3. int main()
  4. {
  5.   using ElemType=int;
  6.   squeue<ElemType,5> QQ;
  7.   ElemType ee;      // 创建一个数据元素。
  8.   cout << "元素(1、2、3)入队。\n";
  9.   ee=1;  QQ.push(ee);
  10.   ee=2;  QQ.push(ee);
  11.   ee=3;  QQ.push(ee);
  12.   cout << "队列的长度是" << QQ.size() << endl;
  13.   QQ.printqueue();
  14.   ee=QQ.front(); QQ.pop(); cout << "出队的元素值为" << ee << endl;
  15.   ee=QQ.front(); QQ.pop(); cout << "出队的元素值为" << ee << endl;
  16.   cout << "队列的长度是" << QQ.size() << endl;
  17.   QQ.printqueue();
  18.   cout << "元素(11、12、13、14、15)入队。\n";
  19.   ee=11;  QQ.push(ee);
  20.   ee=12;  QQ.push(ee);
  21.   ee=13;  QQ.push(ee);
  22.   ee=14;  QQ.push(ee);
  23.   ee=15;  QQ.push(ee);
  24.   cout << "队列的长度是" << QQ.size() << endl;
  25.   QQ.printqueue();
  26. }
复制代码
输出
  1. [root@localhost 07demosqueue]# g++ -o demo1 demo1.cpp _public.h _public.cpp
  2. [root@localhost 07demosqueue]# ./demo1
  3. 元素(1、2、3)入队。
  4. 队列的长度是3
  5. m_data[0],value=1
  6. m_data[1],value=2
  7. m_data[2],value=3
  8. 出队的元素值为1
  9. 出队的元素值为2
  10. 队列的长度是1
  11. m_data[2],value=3
  12. 元素(11、12、13、14、15)入队。
  13. 循环队列已满,入队失败。
  14. 队列的长度是5
  15. m_data[2],value=3
  16. m_data[3],value=11
  17. m_data[4],value=12
  18. m_data[0],value=13
  19. m_data[1],value=14
复制代码
测试步调2
  1. // demo2.cpp,本程序演示基于共享内存的循环队列。
  2. #include "_public.h"
  3. int main()
  4. {
  5.   using ElemType=int;
  6.   // 初始化共享内存。
  7.   int shmid=shmget(0x5005, sizeof(squeue<ElemType,5>), 0640|IPC_CREAT);
  8.   if ( shmid ==-1 )
  9.   {
  10.     cout << "shmget(0x5005) failed.\n"; return -1;
  11.   }
  12.   // 把共享内存连接到当前进程的地址空间。
  13.   squeue<ElemType,5> *QQ=(squeue<ElemType,5> *)shmat(shmid,0,0);
  14.   if ( QQ==(void *)-1 )
  15.   {
  16.     cout << "shmat() failed\n"; return -1;
  17.   }
  18.   QQ->init();       // 初始化循环队列。
  19.   ElemType ee;      // 创建一个数据元素。
  20.   cout << "元素(1、2、3)入队。\n";
  21.   ee=1;  QQ->push(ee);
  22.   ee=2;  QQ->push(ee);
  23.   ee=3;  QQ->push(ee);
  24.   cout << "队列的长度是" << QQ->size() << endl;
  25.   QQ->printqueue();
  26.   ee=QQ->front(); QQ->pop(); cout << "出队的元素值为" << ee << endl;
  27.   ee=QQ->front(); QQ->pop(); cout << "出队的元素值为" << ee << endl;
  28.   cout << "队列的长度是" << QQ->size() << endl;
  29.   QQ->printqueue();
  30.   cout << "元素(11、12、13、14、15)入队。\n";
  31.   ee=11;  QQ->push(ee);
  32.   ee=12;  QQ->push(ee);
  33.   ee=13;  QQ->push(ee);
  34.   ee=14;  QQ->push(ee);
  35.   ee=15;  QQ->push(ee);
  36.   cout << "队列的长度是" << QQ->size() << endl;
  37.   QQ->printqueue();
  38.   shmdt(QQ);  // 把共享内存从当前进程中分离。
  39. }
复制代码
输出
  1. [root@localhost 07demosqueue]# g++ -o demo2 demo2.cpp _public.h _public.cpp
  2. [root@localhost 07demosqueue]# ./demo2
  3. 元素(1、2、3)入队。
  4. 队列的长度是3
  5. m_data[0],value=1
  6. m_data[1],value=2
  7. m_data[2],value=3
  8. 出队的元素值为1
  9. 出队的元素值为2
  10. 队列的长度是1
  11. m_data[2],value=3
  12. 元素(11、12、13、14、15)入队。
  13. 循环队列已满,入队失败。
  14. 队列的长度是5
  15. m_data[2],value=3
  16. m_data[3],value=11
  17. m_data[4],value=12
  18. m_data[0],value=13
  19. m_data[1],value=14
  20. [root@localhost 07demosqueue]# ./demo2
  21. 元素(1、2、3)入队。
  22. 循环队列已满,入队失败。
  23. 循环队列已满,入队失败。
  24. 循环队列已满,入队失败。
  25. 队列的长度是5
  26. m_data[2],value=3
  27. m_data[3],value=11
  28. m_data[4],value=12
  29. m_data[0],value=13
  30. m_data[1],value=14
  31. 出队的元素值为3
  32. 出队的元素值为11
  33. 队列的长度是3
  34. m_data[4],value=12
  35. m_data[0],value=13
  36. m_data[1],value=14
  37. 元素(11、12、13、14、15)入队。
  38. 循环队列已满,入队失败。
  39. 循环队列已满,入队失败。
  40. 循环队列已满,入队失败。
  41. 队列的长度是5
  42. m_data[4],value=12
  43. m_data[0],value=13
  44. m_data[1],value=14
  45. m_data[2],value=11
  46. m_data[3],value=12
复制代码

10. 信号量的根本概念



  • 信号量本质上是一个非负数(>0)的计数器。
  • 用于给共享资源创建一个标记,体现该共享资源被占用情况。
  • P操作(wait)将信号量的值减1,如果信号量的值为0,将阻塞期待,直到信号量的值大于0。
  • V操作(post) 将信号量的值加1,任何时间都不会阻塞。
  • 如果约定信号量的取值只是0和1(0-资源不可用;1-资源可用)可以实现互斥锁。
  • 如果约定信号量的取值体现可用资源的数量,可以实现生产/消费者模型。
  1. // 信号量。
  2. class csemp
  3. {
  4. private:
  5.   union semun  // 用于信号量操作的共同体。
  6.   {
  7.     int val;
  8.     struct semid_ds *buf;
  9.     unsigned short  *arry;
  10.   };
  11.   int   m_semid;         // 信号量id(描述符)。
  12.   // 如果把sem_flg设置为SEM_UNDO,操作系统将跟踪进程对信号量的修改情况,
  13.   // 在全部修改过信号量的进程(正常或异常)终止后,操作系统将把信号量恢复为初始值。
  14.   // 如果信号量用于互斥锁,设置为SEM_UNDO。
  15.   // 如果信号量用于生产消费者模型,设置为0。
  16.   short m_sem_flg;
  17.   csemp(const csemp &) = delete;             // 禁用拷贝构造函数。
  18.   csemp &operator=(const csemp &) = delete;  // 禁用赋值函数。
  19. public:
  20.   csemp():m_semid(-1){}
  21.   // 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
  22.   // 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。
  23.   // 如果用于生产消费者模型,value填0,sem_flg填0。
  24.   bool init(key_t key,unsigned short value=1,short sem_flg=SEM_UNDO);
  25.   bool wait(short value=-1);// 信号量的P操作,如果信号量的值是0,将阻塞等待,直到信号量的值大于0。
  26.   bool post(short value=1); // 信号量的V操作。
  27.   int  getvalue();           // 获取信号量的值,成功返回信号量的值,失败返回-1。
  28.   bool destroy();            // 销毁信号量。
  29. ~csemp();
  30. };
复制代码
  1. // 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
  2. // 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。
  3. // 如果用于生产消费者模型,value填0,sem_flg填0。
  4. bool csemp::init(key_t key,unsigned short value,short sem_flg)
  5. {
  6.   if (m_semid!=-1) return false; // 如果已经初始化了,不必再次初始化。
  7.   m_sem_flg=sem_flg;
  8.   // 信号量的初始化不能直接用semget(key,1,0666|IPC_CREAT)
  9.   // 因为信号量创建后,初始值是0,如果用于互斥锁,需要把它的初始值设置为1,
  10.   // 而获取信号量则不需要设置初始值,所以,创建信号量和获取信号量的流程不同。
  11.   // 信号量的初始化分三个步骤:
  12.   // 1)获取信号量,如果成功,函数返回。
  13.   // 2)如果失败,则创建信号量。
  14.   // 3) 设置信号量的初始值。
  15.   // 获取信号量。
  16.   if ( (m_semid=semget(key,1,0666)) == -1)
  17.   {
  18.     // 如果信号量不存在,创建它。
  19.     if (errno==ENOENT)
  20.     {
  21.       // 用IPC_EXCL标志确保只有一个进程创建并初始化信号量,其它进程只能获取。
  22.       if ( (m_semid=semget(key,1,0666|IPC_CREAT|IPC_EXCL)) == -1)
  23.       {
  24.         if (errno==EEXIST) // 如果错误代码是信号量已存在,则再次获取信号量。
  25.         {
  26.           if ( (m_semid=semget(key,1,0666)) == -1)
  27.           {
  28.             perror("init 1 semget()"); return false;
  29.           }
  30.           return true;
  31.         }
  32.         else  // 如果是其它错误,返回失败。
  33.         {
  34.           perror("init 2 semget()"); return false;
  35.         }
  36.       }
  37.       // 信号量创建成功后,还需要把它初始化成value。
  38.       union semun sem_union;
  39.       sem_union.val = value;   // 设置信号量的初始值。
  40.       if (semctl(m_semid,0,SETVAL,sem_union) <  0)
  41.       {
  42.         perror("init semctl()"); return false;
  43.       }
  44.     }
  45.     else
  46.     { perror("init 3 semget()"); return false; }
  47.   }
  48.   return true;
  49. }
  50. // 信号量的P操作(把信号量的值减value),如果信号量的值是0,将阻塞等待,直到信号量的值大于0。
  51. bool csemp::wait(short value)
  52. {
  53.   if (m_semid==-1) return false;
  54.   struct sembuf sem_b;
  55.   sem_b.sem_num = 0;      // 信号量编号,0代表第一个信号量。
  56.   sem_b.sem_op = value;   // P操作的value必须小于0。
  57.   sem_b.sem_flg = m_sem_flg;
  58.   if (semop(m_semid,&sem_b,1) == -1) { perror("p semop()"); return false; }
  59.   return true;
  60. }
  61. // 信号量的V操作(把信号量的值减value)。
  62. bool csemp::post(short value)
  63. {
  64.   if (m_semid==-1) return false;
  65.   struct sembuf sem_b;
  66.   sem_b.sem_num = 0;     // 信号量编号,0代表第一个信号量。
  67.   sem_b.sem_op = value;  // V操作的value必须大于0。
  68.   sem_b.sem_flg = m_sem_flg;
  69.   if (semop(m_semid,&sem_b,1) == -1) { perror("V semop()"); return false; }
  70.   return true;
  71. }
  72. // 获取信号量的值,成功返回信号量的值,失败返回-1。
  73. int csemp::getvalue()
  74. {
  75.   return semctl(m_semid,0,GETVAL);
  76. }
  77. // 销毁信号量。
  78. bool csemp::destroy()
  79. {
  80.   if (m_semid==-1) return false;
  81.   if (semctl(m_semid,0,IPC_RMID) == -1) { perror("destroy semctl()"); return false; }
  82.   return true;
  83. }
  84. csemp::~csemp()
  85. {
  86. }
复制代码
测试步调3
  1. // demo3.cpp,本程序演示用信号量给共享内存加锁。
  2. #include "_public.h"
  3. struct stgirl     // 超女结构体。
  4. {
  5.   int  no;        // 编号。
  6.   char name[51];  // 姓名,注意,不能用string。
  7. };
  8. int main(int argc,char *argv[])
  9. {
  10.   if (argc!=3) { cout << "Using:./demo no name\n"; return -1; }
  11.   // 第1步:创建/获取共享内存,键值key为0x5005,也可以用其它的值。
  12.   int shmid=shmget(0x5005, sizeof(stgirl), 0640|IPC_CREAT);
  13.   if ( shmid ==-1 )
  14.   {
  15.     cout << "shmget(0x5005) failed.\n"; return -1;
  16.   }
  17.   cout << "shmid=" << shmid << endl;
  18.   // 第2步:把共享内存连接到当前进程的地址空间。
  19.   stgirl *ptr=(stgirl *)shmat(shmid,0,0);
  20.   if ( ptr==(void *)-1 )
  21.   {
  22.     cout << "shmat() failed\n"; return -1;
  23.   }
  24.   // 创建、初始化二元信号量。
  25.   csemp mutex;
  26.   if (mutex.init(0x5005)==false)
  27.   {
  28.     cout << "mutex.init(0x5005) failed.\n"; return -1;
  29.   }
  30.   cout << "申请加锁...\n";
  31.   mutex.wait(); // 申请加锁。
  32.   cout << "申请加锁成功。\n";
  33.   // 第3步:使用共享内存,对共享内存进行读/写。
  34.   cout << "原值:no=" << ptr->no << ",name=" << ptr->name << endl;  // 显示共享内存中的原值。
  35.   ptr->no=atoi(argv[1]);        // 对超女结构体的no成员赋值。
  36.   strcpy(ptr->name,argv[2]);    // 对超女结构体的name成员赋值。
  37.   cout << "新值:no=" << ptr->no << ",name=" << ptr->name << endl;  // 显示共享内存中的当前值。
  38.   sleep(10);
  39.   mutex.post(); // 解锁。
  40.   cout << "解锁。\n";
  41.   // 查看信号量  :ipcs -s    // 删除信号量  :ipcrm sem 信号量id
  42.   // 查看共享内存:ipcs -m    // 删除共享内存:ipcrm -m  共享内存id
  43.   // 第4步:把共享内存从当前进程中分离。
  44.   shmdt(ptr);
  45.   // 第5步:删除共享内存。
  46.   //if (shmctl(shmid,IPC_RMID,0)==-1)
  47.   //{
  48.    // cout << "shmctl failed\n"; return -1;
  49.   //}
  50. }
复制代码
输出
  1. [root@localhost 07demosqueue]# ./demo3 3 冰冰
  2. shmid=2
  3. 申请加锁...
  4. 申请加锁成功。
  5. 原值:no=0,name=
  6. 新值:no=3,name=冰冰
  7. 解锁。
复制代码

11.多历程的生产消费者模型

  1. //incache.cpp
  2. // 多进程的生产消费者模型的生产者程序
  3. #include "_public.h"
  4. int main()
  5. {
  6.   struct stgirl  // 循环队列的数据元素是超女结构体。
  7.   {
  8.     int no;
  9.     char name[51];
  10.   };
  11.   using ElemType=stgirl;
  12.   // 初始化共享内存。
  13.   int shmid=shmget(0x5005, sizeof(squeue<ElemType,5>), 0640|IPC_CREAT);
  14.   if ( shmid ==-1 )
  15.   {
  16.     cout << "shmget(0x5005) failed.\n"; return -1;
  17.   }
  18.   // 把共享内存连接到当前进程的地址空间。
  19.   squeue<ElemType,5> *QQ=(squeue<ElemType,5> *)shmat(shmid,0,0);
  20.   if ( QQ==(void *)-1 )
  21.   {
  22.     cout << "shmat() failed\n"; return -1;
  23.   }
  24.   QQ->init();       // 初始化循环队列。
  25.   ElemType ee;      // 创建一个数据元素。
  26.   csemp mutex; mutex.init(0x5001);     // 用于给共享内存加锁。
  27.   csemp cond;  cond.init(0x5002,0,0);  // 信号量的值用于表示队列中数据元素的个数。
  28.   mutex.wait();  // 加锁。
  29.   // 生产3个数据。
  30.   ee.no=3; strcpy(ee.name,"西施"); QQ->push(ee);
  31.   ee.no=7; strcpy(ee.name,"冰冰"); QQ->push(ee);
  32.   ee.no=8; strcpy(ee.name,"幂幂"); QQ->push(ee);
  33.   mutex.post();  // 解锁。
  34.   cond.post(3);  // 实参是3,表示生产了3个数据。
  35.   shmdt(QQ);  // 把共享内存从当前进程中分离。
  36. }
复制代码
  1. //outcache.cpp
  2. // 多进程的生产消费者模型的消费者程序
  3. #include "_public.h"
  4. int main()
  5. {
  6.   struct stgirl  // 循环队列的数据元素是超女结构体。
  7.   {
  8.     int no;
  9.     char name[51];
  10.   };
  11.   using ElemType=stgirl;
  12.   // 初始化共享内存。
  13.   int shmid=shmget(0x5005, sizeof(squeue<ElemType,5>), 0640|IPC_CREAT);
  14.   if ( shmid ==-1 )
  15.   {
  16.     cout << "shmget(0x5005) failed.\n"; return -1;
  17.   }
  18.   // 把共享内存连接到当前进程的地址空间。
  19.   squeue<ElemType,5> *QQ=(squeue<ElemType,5> *)shmat(shmid,0,0);
  20.   if ( QQ==(void *)-1 )
  21.   {
  22.     cout << "shmat() failed\n"; return -1;
  23.   }
  24.   QQ->init();       // 初始化循环队列。
  25.   ElemType ee;      // 创建一个数据元素。
  26.   csemp mutex; mutex.init(0x5001);     // 用于给共享内存加锁。
  27.   csemp cond;  cond.init(0x5002,0,0);  // 信号量的值用于表示队列中数据元素的个数。
  28.   while (true)
  29.   {
  30.     mutex.wait();  // 加锁。
  31.     while (QQ->empty())    // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if
  32.     {
  33.       mutex.post();   // 解锁。
  34.       cond.wait();    // 等待生产者的唤醒信号。
  35.       mutex.wait();   // 加锁。
  36.     }
  37.     // 数据元素出队。
  38.     ee = QQ->front();  QQ->pop();
  39.     mutex.post(); // 解锁。
  40.     // 处理出队的数据(把数据消费掉)。
  41.     cout << "no=" << ee.no << ",name=" << ee.name << endl;
  42.     usleep(100);    // 假设处理数据需要时间,方便演示。
  43.   }
  44.   shmdt(QQ);
  45. }
复制代码

保举一个零声学院项目课,个人觉得老师讲得不错,分享给大家:
零声白金学习卡(含底子架构/高性能存储/golang云原生/音视频/Linux内核)
https://xxetb.xet.tech/s/3Zqhgt

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

络腮胡菲菲

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表