当前位置: 首页 > news >正文

广元网站建设asp网站开发流程

广元网站建设,asp网站开发流程,百度云自助建站,图片站wordpress模板下载一、匿名管道 我们在之前学习进程的时候就知道了一个概念#xff0c;就是进程间是互相独立的#xff0c;所以就算是两个进程是父子关系#xff0c;其中一个进程退出了也不会影响另一个进程。 也因为进程间是互相独立的#xff0c;所以两个进程间就不能直接的传递信息或者…一、匿名管道 我们在之前学习进程的时候就知道了一个概念就是进程间是互相独立的所以就算是两个进程是父子关系其中一个进程退出了也不会影响另一个进程。 也因为进程间是互相独立的所以两个进程间就不能直接的传递信息或者互相访问各自的资源而如果想要让两个进程之间进行信息传递就需要用到我们今天要讲的“进程间通信”。 实现进程间通信其实有很多种方法我们今天讲的“管道”其实是一种早期的进程间通信方式但是对于我们学习后面的进程间通信具有指导作用。 1.1、进程间通信的本质 进程间不能直接通信但是我们知道操作系统是进程的管理者如果我们想要让两个进程传递信息我们可以让操作系统作为“中间人”啊。 所以一个进程想要和另一个进程传递信息可以先将要传递的信息交给操作系统再由操作系统将信息传递给另一个进程。 那么这个资源就必定会让两个进程都能访问到所以进程间通信的本质是让两个进程看到同一份资源 而这份资源通常是由操作系统提供的而我们进程要讲的“管道”其实就是这一份资源管道其实是一个文件。 1.2、匿名管道的原理 上面说到管道其实是一个文件那么想要通过匿名管道进程进程间通信就一定是要让两个进程同时打开这个匿名管道文件所以一个进程想要个另一个进程进行信息传递就可以先将信息写入到匿名管道文件中另一个进程则是从这个匿名管道文件中读取。 而我们也知道操作系统会为每一个被打开的文件创建一个文件缓冲区所有向该文件中写入的内容都不是直接写入磁盘中而是先存储在文件缓冲区中所以匿名管道文件实际上只是通过缓冲区来实现信息的传递。事实上匿名管道文件也只能通过缓冲区来实现信息传递而且匿名管道文件也并不需要在磁盘中存储。 此外匿名管道文件还有一些特点 1、匿名管道文件是可以被同一个进程打开两次的文件 匿名管道文件打开两次其实并不是同一个匿名管道文件在内存中被加载两次而是同一个匿名管道文件会有两个struct file结构体对象一个用来读、一个用来写分别是读端和写端。 如下图所示 2、匿名管道只能完成具有亲缘关系的进程间的通信 这个特性其实是由匿名管道的“设计”而决定的因为“匿名”管道其实也就是没有名字也没有路径的管道文件因为这个文件没有名字也没有路径这也就注定了其他进程不可能通过名字和路径找到该文件再结合我们之前学过的文件描述符我们也只能通过父进程创建子进程然后子进程继承父进程的文件描述符表的方式让其他进程(子进程)拿到该文件的文件描述符也就相当于找到了这个文件。 如下图所示 2、匿名管道文件只能实现单向通信 其实无论是匿名管道或命名管道都是只支持单向通信的这样设计是为了更方便。 所以一旦确定了数据传输的方向为了保险我们就需要将父子进程多余的不需要的文件描述符关闭比如我们进程想要让父进程向管道里写让子进程从管道中读。那我们就需要将父进程的读端关闭将子进程的写端关闭 所以这也就是为什么一个匿名管道文件被打开的时候会将读端和写端都打开因为只有这样我们才能让子进程也继承到读端和写端后面也就能选择性的关闭相应的文件描述符从而形成特定方向的信道了。 1.3、匿名管道的使用与特性 想要在我们的程序中打开一个匿名管道文件我们首先要认识一个系统调用接口 这个pipe会为我们打开一个匿名管道文件并返回读端和写端的文件描述符只不过返回的文件描述符我们需要使用一个长度为2的文件描述符数组接收也就是上面的pipefd[2]这个pipefd其实是一个输出型参数并且规定pipdfd[0]存储的是读端的文件描述符pipefd[1]存储的是写端的文件描述符。 所以有了上面这个接口和前面的理论我们就可以在我们的程序中创建一个单向通讯的信道了今天我们规定这个信道的方向是父进程读取子进程写入 至此我们就创建好了一个单向通信的信道然后就可以进行进程间通信了。 我们先写一个简单的例子让子进程一直向父进程发送动态的数据并且让父进程读取并打印出来 我们先运行一下试试 但是运行后我们会发现打印出来的信息非常的乱这是因为我们现在的写端写的太快了一下自救把信息给写完了所以读端一读就一下子将所有的信息都读了出来。 所以我们可以让写端写的慢一点让它写完后暂停一秒 我们再次运行 我们看到这次的运行就打印得好多了但是后面确卡死了。 我们先不想为什么会卡死大家难道没发现一个问题吗 我们上面只是让子进程在写的时候暂停了而并没有让父进程也暂停按理说父进程也在一直打印啊但为什么父进程也只打印了10次呢 这是因为操作系统会默认给写端提供“同步”机制也就是说当管道中没有数据时读端必须等待直到管道中有数据为止。 上面的读端虽然一直在读但是我们现在的数据量很小读端一读就将管道中所有的数据全都读完了读完管道也就“清空”了再次读的时候写端还没向管道中写数据所以读端就只能等待。 注意是只有写端哦也就是说只有读端会等待写端而写端不会等待读端就比如我们接下来让写端写的很快而读端读得很慢 那么结果就会打印的非常乱了 这就说明了写端并不会等待读端虽然你读端并没有在读但我写端却一直在写。 那最后为什么又会卡住呢 这是因为正常情况下如果写端关闭读端一直在读read就会返回0表示读到文件结尾。 而我们现在没有多返回值为0做判断所以也就卡住了我们多加一个判断即可 这样进程就能正常退出了 上面是写端关闭的情况那我们让读端中途关闭又会发生什么呢 我们干脆就只让读端只读一次就直接关闭 我们会发现读端在读了一次之后整个进程就直接退出了 这样不好看出是为什么会出退我们可以让父进程在关闭读端后暂停一会然后通过监控脚本来看看子进程的情况 从结果从结果中我们可以看到子进程在父进程关闭读端后直接就崩溃了。 这是因为读端关闭写端一直写。操作系统会直接杀掉写端进程方法是通过向写端发送SIGPIPE(13)信号终止进程。 那么如何证明呢 很简单就是用我们以前学过的进程等待接口waitpid的第二个参数接收进程退出的退出信号 以上就是匿名管道的几个重要的特性讲完了这些之后我们再来理解一下我们操作系统指令中的一个‘|’匿名管道 ‘|’管道相信大家都用过但是可能都不知道原理其实它的原理其实就是我们上面所说到的匿名管道的原理只是多加了一个重定向。 上面的这些指令本质其实都是创建了几个进程然后通过管道进行进程间通信只不过这几个进程都是命令行解释器bash创建的上面的例子不好看出来因为是一执行就退出的使用下面这个例子就会看的清楚一点 同时bash在创建这进程和管道的同时还对这几个进程做了输入输出重定向就拿比较简单的echo | cat来说其结构如下图所示 bash在底层其实是将echo的标准输出和管道的写端进行交换所以此后进程echo所执行的一切输出到显示器中的信息就都会写入到管道文件中而在另一端则是将进程cat的标准输入和管道的读端进行交换所以此后进程cat执行的一切从标准输入中读取数据的工作就都会从管道文件中读取。 所以我们才能看到原本要由echo输出到显示器上的内容反而是被cat打印到了显示器上的 所以我们也就能知道了为什么grep指令能从管道前面执行的结果中过滤出相关信息 二、使用匿名管道实现一个简易的“进程池” 既然我们已经了解了匿名管道的原理那当然也要来做一些实践只有原理和实践结合起来我们才能加深对匿名管道的理解。 其实管道的应用中有一个非常好的例子那就是实现一个“进程池”。 2.1、什么是进程池 大家在学习C的时候可能听说过空间配置器也可能听说过进程池但可能并不真正理解这些到底是个什么东西。 通俗的理解我们可以将进程池理解成一个内存储备库。 我们在学习C语言的时候用过malloc学习C的时候用过new它们两个的作用都是申请一块内存空间供用户使用。 而且我们也知道操作系统是内存的管理者所以分配内存资源的工作只能有操作系统来做而我们要访问操作系统只能通过系统调用。 那么如果我们要频繁使用malloc或new来申请空间难道是每次都要调用系统调用吗 其实用我们目前的知识来分析可以推断出如果是这样做效率其实是不太高的因为调用系统调用也是有消耗的可能单次的影响不是很大但是多次的话就会有一点影响了。 那再结合我们之前学过的缓冲区的知识我们的内存是否也可以像缓冲区一样先申请好一部分资源到用到的时候再分配。 其实是可以的事实上进程池的原理和缓冲区确实很类似比如说我们今天有很多的任务要执行要将这些任务分配给不同的进程去执行那我们就可以事先先创建好一批进程然后从这里进程中选择一些进程执行某些任务没有收到任务的进程就一直等待任务的到来。这样岂不是比需要一个进程再创建一个进程好多了。 而这就是进程池。 那我们今天要怎样实现一个进程池呢我们可以先将图画出来 如上图进程池的创建其实都是由父进程来完成的让父进程一次创建一个管道在创建完一个管道之后立即创建一个子进程然后做好相应的链接工作。 而我们往后要做的是让父进程给子进程派发任务那当然是父进程往管道里写信息然后让子进程来读了所以对于每一个新创建的管道父进程要关闭它的读端子进程要关闭它的读端。 2.2、进程池的模拟实现 2.2.1、创建管道创建进程形成单向信道 第一步我们先要吧管道和子进程先创建出来经过上面的描述完成这一步也不是什么难事 2.2.2、管理管道管理任务 我们上面已经好了管道和进程并形成了单向通信的信道而我们的目标是让父进程可以选择子进程发送任务并让子进程执行那我们就必须将这些管道和子进程管理起来这样将来也好让父进程找到。 所以我们可以设计一个结构体channel在这个结构体中记录一个管道和进程的相关信息然后我们每创建一个管道和子进程就创建一个channel结构体对象。最后再将这些结构体对象统一管理起来比如放到一个数组里面。 我们先来创建这样一个channel结构体类型 可能有的朋友会不知道上面结构体中这个ctrfd具体应该存的是什么那么我在这里做一些解释。 这是因为父进程是需要创建多个管道的而每创建一个管道都会占用父进程的文件描述符表所以之前的图如果画得更详细一点应该是这样的 而父进程往后是要向管道中写入数据那么那么父进程就需要找到每个管道对应的写端文件描述符所以channel结构体中的ctrfd存的就是该管道的写端文件描述符(在父进程中)。 所以我们就可以创建一个管道数组在每次父进程创建完子进程之后就向该数组中加入一个管道对象 完成之后我们可以试着将这个数组打印出来检查 那下一步就是要创建出一些”任务“来让子进程执行对于”任务”呢我们今天就实现得简单一些让子进程输出一些信息就行了例如 不要搞得太复杂意思意思就行了。 而这些任务将来也是需要被父进程找到的所以我们可以顶一个包装器定义一个包装器数组然后将这些任务全都放到这个数组中这样我们以后就可以直接使用数组下标来选择对应的任务了 2.2.3、分配任务 再分配任务给子进程之前我们必须先让子进程一直等待任务的到来而我们之前在说管道的特性的时候说过如果管道中没有数据那么读端就必须等待直到管道中有数据为止所以我们就让子进程一直从管道中读取就行了。 而父进程最终一定是要先选择某一个管道然后再向管道中发送信息让对应的子进程执行某一个任务而我们刚才又已经将任务以数组的方式组织起来了所以子进程需要读的就只是某一个任务对应的下标即可 而如果再读的过程中写端关闭了根据之前总结的特性读端会读到0那既然写端都关闭了那我们就不需要再读了直接让子进程跳出循环然后退出即可。 而我们父进程需要做的就是选择管道选择任务最后只需要向对应的管道中发送任务的下标即可。 选择的方式有很多种我们可以随机的选择也可以以轮询的方式选择我们今天以为轮询的方式选择管道然后以随机的方式选择任务 运行结果 当然我们差最后一步那就是在进程退出之前将所有的管道关闭和所有的子进程回收而我们已经将进程和管道的信息都保存在了channels数组中了所以只需要遍历一遍channels数组即可 运行结果 而上面我为什么要遍历两次channels数组而不是只遍历了一遍以及为什么需要先关闭管道再回收进程这就得好好说一下了因为这里面还藏着一个隐藏的非常深的bug。 2.2.4、处理一个比较“深”的bug 如果我们现在先回收子进程再关闭管道我们就能看到这个bug了 我们会发现最后程序卡住了 同样的如果我们将回收子进程和关闭管道放在同一个循环内也会出现这样的问题 那这个bug到底出在哪呢 很多朋友可能会以为bug处在我们回收进程和关闭管道这里但却并不是事实上bug是出在了我们最开始创建管道和进程的地方 想要弄清楚这个bug那可得好好的画图分析了 我们知道父进程再创建一个子进程的时候子进程会继承父进程的文件描述符表 如上图父进程创建出子进程子进程会继承下来父进程的文件描述符表只是后来父进程和子进程会关闭对应的读写端。 那么这第一个管道是没什么问题的但是到第二个管道就会出问题了 如上图进程2除了会继承到管道2的读端和写端文件描述符同时还会多继承一个管道1的文件描述符。 同理类推如果还有进程3那进程3除了继承管道3的读写端还会额外的继承下来管道1和管道2的写端文件描述符。 以此类推往后每一个新进程都会继承前面创建的所有管道的写端。 那这又会造成什么后果呢根据我们之前总结的匿名管道的特性如果写端关闭读端会一直读读端0表示读到文件结尾但是就拿上面的情况来说如果我们是吧关闭文件描述符和回收子进程的工作放在同一个循环中 那么 就相当于只是关闭了管道1的一个写端(在父进程的文件描述符表)但其实还有一个写端是子进程2还没有关闭所以子进程1的读端就不会读到0也就不会退出 那要怎么解决这个问题呢 其实我们上面写的先循环关闭管道再循环回收进程的方法就是一种解决方案 这是因为无论我们有多少个管道被创建出来最后一个管道都是只有一个写端指向它那么虽然我们现在是从前往后关闭父进程的写端但最终都是会关闭最后一个管道的写端 如上图管道2的唯一写端被关闭了子进程2就会从管道中读到0那么子进程就退出了 而子进程2退出了子进程2的所有文件描述夫也就关闭了也就导致了管道1的写端也全关闭了那么子进程1也就读到0退出。 所以这是一个”从前往后“再从后往前的过程以此类推以后我们有十个八个管道也是这样子。 而经过以上分析我们的第二种方案也就出来了就是从后往前遍历channels数组然后在一个循环中先关闭文件描述符再等待子进程 运行结果 但是不知道大家有没有感觉到别扭就是我们新建的子进程为什么要继承这些额外的没有必要的文件描述符呢为什么不能把多余的文件描述符都关闭呢 那就要引出我们的第三种方案了这也是我觉得最优的一种方案那就是将每个子进程多余的文件描述符都管掉 具体做法 因为父进程的文件描述符表中是保存着所有的管道的写端文件描述符的所以我们可以将父进程中所有的管道写端文件文件描述符拷贝到一个数组中那么在每创建一个一个子进程之后就将该数组中旧的文件描述符关掉即可最后再由父进程将新管道的写端文件描述符加入到这个数组中 这样就算我们吧关闭和回收放在一个循环中也是OK的 完整代码 #include iostream #include vector #include cstring #include string #include unistd.h #include fcntl.h #include cassert #include functional #include ctime #include sys/types.h #include sys/wait.h using namespace std; const int num 5; int channel_number 1; // 管道的编号之后构成管道的名称 typedef functionvoid() task;// 任务 void Play_Music() {cout 子进程 getpid() 正在执行播放音乐的任务…… endl; } void Play_Video() {cout 子进程 getpid() 正在执行播放视频的任务…… endl; }void Print_Log() {cout 子进程 getpid() 正在执行打印日志的任务…… endl; }void Download() {cout 子进程 getpid() 正在执行下载任务…… endl; }// channel结构体类型 struct channel {// 构造函数channel(int fd, pid_t id) :ctrfd(fd),workid(id){name channel- to_string(channel_number);}int ctrfd; // 控制该管道的文件描述符pid_t workid; // 链接该管道的子进程idstring name; // 管道的名称 };int main() {vectorchannel channels; // 管道数组vectortask tasks; // 任务数组// 初始化任务数组tasks.push_back(Play_Music);tasks.push_back(Play_Video);tasks.push_back(Print_Log);tasks.push_back(Download);vectorpid_t oldfd; // 保存旧的文件描述符// 创建进程创建管道for (int i 0; i num; i) {// 创建管道int pipefd[2] { 0 };int res pipe(pipefd);assert(res 0);// 创建进程pid_t id fork();assert(id ! -1);if (id 0) {// 先关闭所有旧的文件描述符if (!oldfd.empty()) {for (auto fd : oldfd) {close(fd);}}// 子进程close(pipefd[1]); // 子进程读子进程关闭写端while (true) {int index 0;int n read(pipefd[0], index, sizeof(index));if (n sizeof(index)) {// 读取成功执行任务tasks[index]();} else if (n 0) {// 说明写端已经关闭则子进程停止break;}}exit(0); // 让子进程执行完就退出}// 父进程close(pipefd[0]); // 父进程写父进程关闭读端channels.push_back(channel(pipefd[1], id));oldfd.push_back(pipefd[1]);}// 种一颗随机数种子srand(time(NULL) * getpid());const int T 10; // 让父进程选择T次for (int i 0; i T; i) {int channeli i % num; // 管道下标int taski rand() % tasks.size(); // 任务下标channel c channels[channeli];// 向管道发送下标write(c.ctrfd, taski, sizeof(taski));sleep(1);}// 关闭管道回收子进程// 方案一// for (auto c : channels) {// close(c.ctrfd);// cout close c.name sucess…… endl;// }// for (auto c : channels) {// pid_t rid waitpid(c.workid, nullptr, 0);// if (rid c.workid) {// cout wait child process: rid sucess…… endl;// }// }// // 方案二// for (int i channels.size() - 1; i 0; i--) {// close(channels[i].ctrfd);// cout close channels[i].name sucess…… endl;// pid_t rid waitpid(channels[i].workid, nullptr, 0);// if (rid channels[i].workid) {// cout wait child process: rid sucess…… endl;// }// }// 方案三(配合oldfd)for (auto c : channels) {close(c.ctrfd);cout close c.name sucess…… endl;pid_t rid waitpid(c.workid, nullptr, 0);if (rid c.workid) {cout wait child process: rid sucess…… endl;} }return 0; } 三、命名管道 讲完了匿名管道命名管道其实就很好理解了因为与原理都是一样的事实上命名管道的操作也确实比匿名管道要简单。 3.1、指令级的命名管道 所谓命名管道也就是有名字的管道前面说过管道其实就是个文件那么命名管道就是一个有文件名的文件。 我们首先来看看指令级级的命名管道在系统中有一个mdfifo的指令它可以用来创建一个命名管道 用它就可以来创建一个命名管道文件 前面说过匿名管道只能实现具有亲缘关系的进程之间的通信但是命名管道可以实现两个毫不相关的进程之间的通信究其原因就是因为命名管道它有文件名和路径而有了文件名和路径其实就可以唯一确定一个文件。所以命名管道并不像匿名管道一样只能通过继承文件描述符的方式来让两个进程看到同一个文件。 所以我们可以开两个终端来试验命名管道 再结合我们前面的理论进程间通信的本质是让两个进程看到同一份资源所以我们要做的就是让两个终端同时打开这个fifo。 然后让一个终端写入一个终端读取就完成了两个毫不相关的进程间通信了。 但是当我们查看这个命名管道文件的时候却会发现这个文件的大小并没有改变 这是因为命名管道和匿名管道一样并不用再磁盘中存储他们都只是通过文件缓冲区来传输数据而已。 3.2、代码级的命名管道 然后我们再来聊聊代码级的命名管道操作系统其实还给我们提供了一个系统调用接口名也是mkfifo它可以在我们的代码中创建一个命名管道文件 而在代码上既然要是两个毫不相关的进程那我们就要创建两个独立的可执行文件我们分别命名为sever和cilent即服务端和客户端然后我们让server端从管道中读取让client端向管道中写入。 我们先让server端来创建这个命名管道文件 然后client端就只需要打开这个文件即可 同样的server也需要以只读方式打开这个文件 然后我们运行一下试试因为是server端创建的管道文件所以我们需要先运行server端 但是我们会发现只运行server端只会打印出创建管道文件成功后面的打开文件的信息就没有打印了而当我们再运行client端后就成功打开了 这很好地说明了命名管道也是需要同步的两端都打开了才能进程通信如果其中一端没有打开另一端也不会打开。 然后我们就可以来进行进程间通信了我们今天想要实现一个两个窗口互发信息的场景所以我们让client端从键盘中读入然后在写到管道中 然后server端只需要从管道中读出数据并打印出来就行了 运行结果 做完实验后我们就会发现命名管道和匿名管道除了一个没名字一个有名字之外其实都一样包括读端会等待写端写端关闭读端会读端到0这些……所以代码也几乎是老代码。
http://www.pierceye.com/news/687772/

相关文章:

  • 站长平台怎么添加网站网站中文名注册
  • 唐四薪php网站开发答案想制作一个网站要多少钱
  • ppt网站建设答案东台网页定制
  • 做网站 侵权如何制作手机版网站
  • 代发网站建设app网站软件
  • 家居企业网站建设如何国家企业信息公示系统全国
  • 网站平台如何推广wordpress登录页面显示ip
  • 如何做网站百度排名优化推广的目的是什么
  • 重庆忠县网站建设公司哪家专业芷江建设局的工作人员网站
  • 戴尔电脑网站建设方案范文室内设计方案图
  • 餐厅网站建设策划方案网站建设存在的具体问题
  • 竞价页面网站做优化广告商对接平台
  • 网站后台如何修改密码wordpress调整页面布局
  • 东莞热点网站建设莱州人才网
  • 线上渠道推广网站的优化哪个好
  • 群晖可以做网站服务器微信朋友圈怎么发链接那种网页怎么制作
  • wordpress 公司网站网站和公众号的区别
  • 数据库跟网站seo流程
  • 网站首页关键词设置网站的整体风格
  • wordpress源码站整站源码制作wordpress插件
  • 海口网站建设就q479185700上墙网站空间后台怎么进入
  • 四川电子有限公司 - 手机网站如何做网站家具导购
  • 网站经营性备案修改wordpress自带小工具
  • 网站怎么建设可以发图评论网站建设制作公
  • 做销售的网站设计公司是做什么的
  • 建设专业网站的利弊海淀区seo招聘信息
  • 吴江区住房与建设局网站网站开发怎么对接客户
  • frontpage2007网站建设网站开发需要redis
  • 国内做网站多少钱ui中有哪些做的好看的网站
  • 金湖建设工程质量监督网站网页设计 网站建设 哪个好