关于进程:进程最后的遗言

过程最初的遗嘱前言在本篇文章当中次要给大家介绍父子过程之间的关系,以及他们之间的交互以及可能造成的状态,帮忙大家深刻了解父子过程之间的关系,以及他们之间的交互。 僵尸过程和孤儿过程僵尸过程在 Unix 操作系统和类 Unix 操作系统当中,当子过程退出的时候,父过程能够从子过程当中获取子过程的退出信息,因而在 类 Unix 操作系统当中只有父过程通过 wait 零碎调用读取子过程的退出状态信息之后,子过程才会齐全退出。那么子过程在程序执行实现之后(调用 _exit 零碎调用之后),到父过程执行 wait 零碎调用获取子过程退出状态信息之前,这一段时间的过程的状态是僵尸过程的状态。 正式定义:在 Unix 或者类 Unix 操作系统当中,僵尸过程就是哪些曾经实现程序的执行(实现_exit 零碎调用退出了程序),然而在内核当中还有属于这个过程的过程表项。这个表项的作用次要是让父过程读取子过程的退出状态信息 (exit status)。 在后文当中咱们有一个例子详细分析这个退出状态的相干信息。一旦父过程通过 wait 零碎调用读取完子过程的 exit statis 信息之后,僵尸过程的过程表项就会从过程表(process table)当中被移除,这个过程就算是彻底沦亡了(reaped)。 如果零碎当中有很多处于僵尸状态的过程而父过程又没有应用 wait 零碎调用去失去子过程的退出状态,那么零碎当中就会有很多内存没有被开释,这就会导致资源泄露。 上面是一个僵尸过程的例子,对应的代码名称为 Z.c: #include <stdio.h>#include <unistd.h>int main() { printf("parent pid = %d\n", getpid()); if(fork()) { while(1); } printf("child process pid = %d\n", getpid()); return 0;}下面C语言对应的python代码如下: import osif __name__ == "__main__": print(f"parent pid = {os.getpid()}") pid = os.fork() if pid != 0: # parent process will never exit while True: pass # child process will exit print(f"child process pid = {os.getpid()}")当初执行下面的程序,失去的后果如下所示: ...

October 28, 2022 · 4 min · jiezi

关于进程:用闪电侠解释一下进程和线程

1.艾伦在一次粒子加速器爆炸大事变中取得了极速挪动的超能力,因而开始化身为超级英雄“闪电侠”。类比之下,CPU是计算机最外围的部件,它负责指令的读取和执行,每秒能够执行几十亿条指令!其实比闪电侠还要快得多。 小闪这种能力很快就被FBI发现了,为了好好利用小闪,FBI雇佣了小闪为其特地口头小组A执行工作。 说是特地口头小组,其实除了小闪之外只有一个A博士,小闪日常的工作就是获得A博士的指令并且执行。这就是计算机晚期的单过程模型。 2. 然而A博士从搜集情报到得出正确的指令毕竟须要工夫,而小闪执行指令的速度又太快,所以在FBI高层眼里,A博士总是忙忙碌碌,而小闪成天优哉游哉。 为了进步小闪的利用率,FBI在特地口头小组办公室的楼下,着手成立另一个特地口头小组B。 这个着手成立的过程就是编码,而编码的后果就是失去一个可能实现某个特定性能的程序。很快,特地口头小组B在B博士的单独主持下开始暗中运行。这就是过程的诞生,过程其实就是运行的程序。当初FBI特地口头小组进入了多过程时代。 3.尽管都是FBI的特地口头小组,若无非凡状况,A和B通常井水不犯河水,他们都认为本人垄断了FBI的所有资源,但这其实只是FBI的小把戏而已。 背地的含意就是每个过程采纳了完全相同的虚拟地址空间,然而经由操作系统和硬件MMU合作,映射到不同的物理地址空间。 不同的过程,都有各自独立的物理内存空间,特地口头小组A和B之间的通信就是过程间通信(IPC)。 4.小闪尽管速度快,然而仍然没方法在同一时刻同时执行A博士和B博士两个人的指令,就如同人不能在向右看的同时向左看。 A博士和B博士经常为此大打出手,谁都想占用小闪更多的工夫,好实现本人的KPI。 FBI领导层想了一个方法,新成立了一个调度小组,用来给各个小组调配小闪的应用工夫。一开始,调度小组会给每个过程调配相等的一小段时间,而后每个小组轮流地占用小闪执行相应工夫的工作。这就是CPU的工夫片调配。 如果小闪在这一小段时间内还没执行完,那也必须得停,然而得保留一下执行进度,下次持续从完结的中央开始做。这就是CPU的上下文切换。 这样一来,A博士和B博士的KPI实现了,小闪也忙起来了。说是忙起来,然而花在指令执行上的工夫其实也没多多少,根本就是在两个小组之间重复横跳了,小闪的工作仍然惬意。 5.两个博士眼红于小闪仍然有大把的闲暇工夫,竟然颇有默契地发动了招聘布告,广揽天下英才,势必要多找点事件给小闪做。 很快,口头小组内的成员越来越多,并且每个成员的工作都不一样,比方有些人负责查阅材料,有些人负责收取讯息......小组内各个成员分工协作,实现特地小组的独特指标。自此进入多线程时代。 线程就好比是小组内的成员,一个过程能够蕴含很多个线程。 过程是资源分配的根本单位,比方FBI给特地小组调配办公场合。 线程是CPU调度的根本单位,比方小闪须要执行每个小组成员的指令。 6. 成员多了,治理就成了一个新的问题。如果每个成员只是自说自话,齐全不考究团队合作,极容易造成团队外部抵触。 为此,FBI制订了几个策略。对应的是线程的同步。 7. 口头小组内的资源不同,共享的水平也不一样。比方厕所,当有人正在应用的时候,其他人只能期待,如果贸然闯进去必然呈现抵触。这示意过程中的某些共享内存同一时间只能由一个线程应用,其余线程必须期待该线程完结应用之后能力持续应用。 一个避免其他人进入的简略办法就是给厕所增加一把锁,首先占用厕所的人上锁,其他人看到有锁之后就在门口排队,直到占用的线程开释锁能力进入。这个策略叫做「互斥锁」,英文叫做Mutex。 8. 不同于厕所,会议室就能同时容许10集体进入,如果人数超过10个,多进去的人只能排队等着,除非有人空出地位,其他人能力进入会议室。 为了解决这个问题,FBI在会议室的门口挂了10把钥匙,每个人进入会议室前都要取一把钥匙,进去时把钥匙放回原位。如果后来者发现没有钥匙了,就在会议室门口期待。这种策略叫做「信号量」,互斥锁只容许一个线程进入临界区,信号量容许多个线程同时进入临界区。 9. 有些时候,口头小组的某些工作比较复杂,须要流水线式作业。上游的人员做完之后把后果交付给上游人员解决,这就是典型的生产者消费者模式。 如果生产者生产得太快,咱们能够适当让上游的人员进行作业,期待某个机会唤醒生产者;反之,如果消费者生产得太快,咱们能够适当让上游的人员进行作业,等到某个机会唤醒消费者。 这种策略叫做「条件变量」,背地的原理是当线程在期待某些条件时使线程进入睡眠状态,一旦条件满足,就唤醒。 10. 最初拿口头小组的放映机举个例子。很多成员喜爱在休息时间坐在一起应用放映机看个电影,消遣一下工夫,相似于多线程对同一资源进行读操作,这种状况下不论多少人在看电影都不会呈现问题。 然而偏偏有人在其他人看电影的时候要降级一下放映机的操作系统,这必定会影响其他人的观影体验;反之,在降级操作系统的时候,有人要看电影,这同样会对降级人员造成困扰。 这种时候咱们能够定一个策略,当观影时,随时欢迎其余观影人员应用观看;当降级时,禁止任何观影人员和任何其余降级人员应用。 这种形式称为「读写锁」,也叫做「共享-独占锁」,“观影”对应的就是线程的读操作,“降级”对应的就是线程的写操作。具体来说个别有两种状况: 读写锁处于写锁定的状态,则在解锁之前,所有试图加锁的线程都会阻塞;读写锁处于读锁定的状态,则所有试图以读模式加锁的线程都可失去拜访权,然而以写模式加锁的线程则会阻塞;作者:蝉沐风公众号:蝉沐风欢送我,邂逅更多精彩文章完!

August 5, 2022 · 1 min · jiezi

关于进程:每日一篇730进程调度算法

调度算法的意义程序运行时,通常会有多个过程或线程同时竞争CPU,然而如果只有一个CPU可用,那就必须抉择下一个要运行的过程。在操作系统中实现抉择工作的一部分叫做调度程序。然而过程也有辨别,CPU密集型:大多工夫在计算IO密集型:大多工夫在IO期待切换(因为当初CPU的性能进步 ,次要是IO密集型)调度算法的目标就是为了放弃零碎所有局部尽可能繁忙。次要有四个指标掂量调度算法的好坏,吞吐量、周转工夫、CPU利用率、响应工夫。并且调度程序须要思考到CPU利用率,因为过程的切换比拟高,用户态必须切换到内核态,而后保留以后过程的状态,包含在过程表中存储寄存器值以便当前从新装载。调度算法分类:批处理、交互式、实时 批处理零碎的调度先来先服务First-come first-served,依照过程申请的程序应用CPU。相当于有一个就绪过程的繁多队列。长处:易于了解便于在程序中使用。毛病:如果某个过程过大,会导致前面过程饥饿。 短作业优先:毛病:对长过程不敌对。 最短剩余时间优先是短作业优先的抢占式版本,总是抉择残余运行工夫最短的那个过程运行,须要对整个事件同以后过程的剩余时间比拟,如果右小于以后过程的,以后过程就会被挂起,运行新的过程。 交互式零碎的调度轮转调度每个过程都会被分一个时间段,即工夫片,容许该过程在该工夫片内运行。调度程序须要保护一张可运行过程列表。:毛病:工夫片设置的过长会导致短的交互申请的响应工夫变长,过短会导致过多过程切换升高CPU效率。 优先级调度优先级能够是动态赋予也能够是动静赋予。比方将优先级设为该过程在上一时间片所占局部的倒数。毛病:可能会导致低优先级过程产生饥饿景象。须要偶然堆优先级进行调整。 彩票调度实时零碎的调度实时零碎是工夫起主导作用的零碎。 硬实时即必须满足相对的截止工夫。将程序划分为一组过程实现,每个过程的行为都是能够预测和提前把握的。 软实时

July 31, 2022 · 1 min · jiezi

关于进程:一道进程相关面试题引发的思考

“ 小明同学最近贼郁闷,去年玩比特币亏得一塌糊涂,想在股市里翻盘,听信大V举荐的股票,买了康美和长生生物,被A股狠狠的收割了一把。本想往年好好工作,谁晓得遇上了O记大裁员。尽管拿了N+6,然而还是要找工作养家糊口啊!只能硬着头皮去面试了,还好,面试题目都不难,因为小明认真看过《奔跑吧》” 01 面试题目 在一个双核处理器的零碎中,在shell界面下运行test程序。CPU0的就绪队列上有4个过程,而CPU1的就绪队列有1个过程。假如test程序和这个5个过程的nice值都是为0。 test程序 执行test过程 问题: 请画出test程序在内核空间的执行流程图。若干工夫之后,CPU0和CPU1的就绪队列变动如何?小明听到这题目,嘿嘿一笑,so easy,难不到窝,这题目比炒A股简略多了!于是在黑板上开始边写边画,缄口结舌。 02 题目解析 站在用户空间的角度看,在shell界面下执行test程序,shell程序会调用fork零碎调用来创立一个新过程,而后调用exec零碎调用来装载test过程,因而新过程便开始执行test程序。 站在用户空间的角度看问题,咱们只能看到test程序被执行了,然而咱们是看不到新过程是如何被创立的,它会增加到哪个CPU里,它是如何执行的,以及CPU0和CPU1之间如何做负载平衡等等问题。 小明画的test过程执行流程图 从上图可知,咱们把test过程在内核空间的执行过程分成了6个步骤。 (1)调用零碎调用fork零碎调用来创立一个新过程 (2)do_fork()创立新过程。do_fork要做好多事件,比方: a)创立新过程过程管制块PCB,task_struct数据结构。 b)拷贝父过程的task_struct数据结构内容到新过程。 c)拷贝父过程的页表项内容到新过程。 d)设置新过程的内核栈 (3)父过程调用wake_up_new_task()尝试去唤醒新过程。 a)调用调度类的select_task_rq()办法,为新过程寻找一个负载最轻的CPU,这里抉择CPU1。 b)调用调度类的enqueue_task()办法把新过程增加到CPU1的就绪队列里。 (4)CPU1从新抉择一个适合的过程来运行。 a)每次时钟滴答到来时,scheduler_tick()会调用调度类的task_tick()去查看是否须要从新调度。check_preempt_tick()查看是否须要从新调度。若须要调度,则设置以后过程的thread_info中的TIF_NEED_RESCHED标记位。假如在咱们这个场景里,CPU1筹备调度咱们的新过程P,那么就会设置以后过程curr的thread_info中的TIF_NEED_RESCHED标记位。 b)在中断返回前会查看以后过程curr的TIF_NEED_RESCHED标记位。如果须要调度的话,调用preempt_schedule_irq()来切换过程运行。 c)调度器的schedule()函数会调用调度类的pick_next_task()办法来抉择下一个最合适运行的过程。在咱们的场景中,抉择新过程P。 d)switch_mm()切换prev过程和新过程的页表。 e)在CPU1上,switch_to()切换新过程P来执行。 (5)新过程执行。 a)新过程的第一次执行会调用ret_from_fork()函数。 b)返回用户空间执行shell程序。 c)shell程序调用exec()零碎调用来执行test程序,最终新过程变成了test过程。 (6)负载平衡。 a)在每次时钟滴答到来时去查看是否须要触发软中断来做SMP负载平衡。scheduler_tick()->trigger_load_balance()。下一次做负载平衡的工夫点寄存在就绪队列的next_balance成员里。 b)触发SCHED_SOFTIRQ软中断。 c)在软中断处理函数run_rebalance_domains()里,从以后CPU开始遍历CPU拓扑关系图,从调度域的低层往高层遍历调度域,并寻找有负载不平均的调度组。本例子中的CPU拓扑关系图很简略,只有一层MC级别的调度域。 d)CPU0对应的调度域是domain_mc_0,对应的调度组是group_mc_0;CPU1对应的调度域是domain_mc_1,对应的调度组是group_mc_1。CPU0的调度域domain_mc_0管辖CPU0和CPU1,其中group_mc_0和group_mc_1这两个调度组会连贯到domain_mc_0的一个链表里,同理CPU1的调度域domain_mc_1也是同样治理着group_mc_1和group_mc_0这两个调度组。 e)假如以后运行的CPU是CPU1,也就是说运行到run_rebalance_domains()函数的CPU为CPU1,那么在以后MC的调度域(domain_mc_1)里去找哪个调度组是最忙碌的。在咱们的场景里,很容易找到CPU0的那个调度组(group_mc_0)是最忙碌的。计算须要迁徙多少的负载量到CPU1上能力放弃两个调度组负载平衡。 f)迁徙过程从CPU0到CPU1。 g)达到新的均衡。 笨叔点评: 这是一道很棒的面试题目,把过程方方面面的问题都考到了,比如说过程的创立,过程的调度,过程的执行,SMP负载平衡。外面任何的一点,都足以难倒很多人,比如说: 过程的实质是什么? 过程的调度实质是什么? 调度器怎么抉择下一个过程? 处理器是怎么就切换和执行到下一个过程? CFS调度器和过程优先级到底有什么关系? CFS调度器里的虚构时钟是个什么鬼? runnable和running是啥关系? 什么是过程的权重? 什么是过程的负载? 调度域和调度组是个什么鬼? 如何能疾速画出一个零碎的CPU拓扑关系图? SMP负载平衡里是怎么掂量一个就绪队列的负载的? SMP负载平衡算法是怎么玩的? 负载和权重到底有什么关系? Linux的负载平衡算法里说的负载是怎么计算的? 怎么了解衰减(decay)? ... 笨叔正在批改蓝色版本的《奔跑吧》,基于Linux 5.0+ARM64,欢送大家提意见和idea!好的idea,笨叔好酒相赠! 更多精彩内容,关注笨叔的微信公众号以及笨叔免费旗舰篇视频。 ...

April 2, 2022 · 1 min · jiezi

关于进程:图文介绍进程和线程的区别

过程和线程的概念 先理解一下操作系统的一些相干概念,大部分操作系统(如Windows、Linux)的任务调度是采纳工夫片轮转的抢占式调度形式,也就是说一个工作执行一小段时间后强制暂停去执行下一个工作,每个工作轮流执行。工作执行的一小段时间叫做工夫片,工作正在执行时的状态叫运行状态,工作执行一段时间后强制暂停去执行下一个工作,被暂停的工作就处于就绪状态期待下一个属于它的工夫片的到来。这样每个工作都能失去执行,因为CPU的执行效率十分高,工夫片十分短,在各个工作之间疾速地切换,给人的感觉就是多个工作在“同时进行”,这也就是咱们所说的并发(并发简略来说多个工作同时执行)。过程 计算机的外围是CPU,它承当了所有的计算工作;而操作系统是计算机的管理者,它负责工作的调度、资源的调配和治理,统领整个计算机硬件;应用程序侧是具备某种性能的程序,程序是运行于操作系统之上的。 过程是一个具备肯定独立性能的程序在一个数据集上的一次动静执行的过程,是操作系统进行资源分配和调度的一个独立单位,是利用程序运行的载体。过程是一种形象的概念,素来没有对立的规范定义。过程个别由程序、数据汇合和过程管制块三局部组成。程序用于形容过程要实现的性能,是管制过程执行的指令集;数据汇合是程序在执行时所须要的数据和工作区;程序控制块(Program Control Block,简称PCB),蕴含过程的形容信息和管制信息,是过程存在的惟一标记。 过程具备的特色: 动态性:过程是程序的一次执行过程,是长期的,有生命期的,是动静产生,动静沦亡的; 并发性:任何过程都能够同其余过程一起并发执行; 独立性:过程是零碎进行资源分配和调度的一个独立单位; 结构性:过程由程序、数据和过程管制块三局部组成。 过程的生命周期 在晚期只有过程的操作系统中,过程有五种状态,创立、就绪、运行、阻塞(期待)、退出。创立:过程正在创立,还不能运行。操作系统在创立过程时要进行的工作包含调配和建设过程管制块表项、建设资源表格并分配资源、加载程序并建设地址空间; 就绪:工夫片已用完,此线程被强制暂停,期待下一个属于他的工夫片到来; 运行:此线程正在执行,正在占用工夫片; 阻塞:也叫期待状态,期待某一事件(如IO或另一个线程)执行完; 退出:过程已完结,所以也称完结状态,开释操作系统调配的资源。 线程 在晚期的操作系统中并没有线程的概念,过程是能领有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采纳的是工夫片轮转的抢占式调度形式,而过程是任务调度的最小单位,每个过程有各自独立的一块内存,使得各个过程之间内存地址互相隔离。 起初,随着计算机的倒退,对CPU的要求越来越高,过程之间的切换开销较大,曾经无奈满足越来越简单的程序的要求了。于是就创造了线程,线程是程序执行中一个繁多的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的根本单位。一个过程能够有一个或多个线程,各个线程之间共享程序的内存空间。 一个规范的线程由线程ID、以后指令指针(PC)、寄存器和堆栈组成。而过程由内存空间(代码、数据、过程空间、关上的文件)和一个或多个线程组成。 线程的生命周期 当线程的数量小于处理器的数量时,线程的并发是真正的并发,不同的线程运行在不同的处理器上。但当线程的数量大于处理器的数量时,线程的并发会受到一些妨碍,此时并不是真正的并发,因为此时至多有一个处理器会运行多个线程。在单个处理器运行多个线程时,并发是一种模仿进去的状态。操作系统采纳工夫片轮转的形式轮流执行每一个线程。当初,简直所有的古代操作系统采纳的都是工夫片轮转的抢占式调度形式,如咱们相熟的Unix、linux、Windows及Mac OS X等风行的操作系统。创立:一个新的线程被创立,期待该线程被调用执行; 就绪:工夫片已用完,此线程被强制暂停,期待下一个属于他的工夫片到来; 运行:此线程正在执行,正在占用工夫片; 阻塞:也叫期待状态,期待某一事件(如IO或另一个线程)执行完; 退出:一个线程实现工作或者其余终止条件产生,该线程终止进入退出状态,退出状态开释该线程所调配的资源。 线程优先级 操作系统(如Windows、Linux、Mac OS X)的任务调度除了具备后面提到的工夫片轮转的特点外,还有优先级调度(Priority Schedule)的特点。优先级调度决定了线程依照什么程序轮流执行,在具备优先级调度的零碎中,线程领有各自的线程优先级(Thread Priority)。具备高优先级的线程会更早地执行,而低优先级的线程通常要等没有更高优先级的可执行线程时才会被执行。 线程的优先级能够由用户手动设置,此外零碎也会依据不同情景调整优先级。通常状况下,频繁地进入期待状态(进入期待状态会放弃之前仍可占用的工夫份额)的线程(如IO线程),比频繁进行大量计算以至于每次都把所有工夫片全副用尽的线程更受操作系统的欢送。因为频繁进入期待的线程只会占用很少的工夫,这样操作系统能够解决更多的工作。咱们把频繁期待的线程称之为IO密集型线程(IO Bound Thread),而把很少期待的线程称之为CPU密集型线程(CPU Bound Thread)。IO密集型线程总是比CPU密集型线程更容易失去优先级的晋升。 线程饿死 在优先级调度下,容易呈现一种线程饿死的景象。一个线程饿死是说它的优先级较低,在它执行之前总是有比它优先级更高的线程期待执行,因而这个低优先级的线程始终得不到执行。当CPU密集型的线程优先级较高时,其它低优先级的线程就很可能呈现饿死的状况;当IO密集型线程优先级较高时,其它线程绝对不容易造成饿死的,因为IO线程有大量的等待时间。为了防止线程饿死,调度零碎通常会逐渐晋升那些期待了很久而得不到执行的线程的优先级。这样,一个线程只有它期待了足够长的工夫,其优先级总会被晋升到能够让它执行的水平,也就是说这种状况下线程始终会失去执行,只是工夫的问题。 在优先级调度环境下,线程优先级的扭转有三种形式: 1.用户指定优先级; 2.依据进入期待状态的频繁水平晋升或升高优先级(由操作系统实现); 3.长时间得不到执行而被晋升优先级。 多线程与多核 下面提到的工夫片轮转的调度形式说一个工作执行一小段时间后强制暂停去执行下一个工作,每个工作轮流执行。很多操作系统的书都说“同一时间点只有一个工作在执行”。其实“同一时间点只有一个工作在执行”这句话是不精确的,至多它是不全面的。咱们剖析一下多核的状况。 这是我的电脑的CPU状况图:多核(心)处理器是指在一个处理器上集成多个运算外围从而进步计算能力,也就是有多个真正并行计算的解决外围,每一个解决外围对应一个内核线程。内核线程(Kernel Thread, KLT)就是间接由操作系统内核反对的线程,这种线程由内核来实现线程切换,内核通过操作调度器对线程进行调度,并负责将线程的工作映射到各个处理器上。个别一个解决外围对应一个内核线程,比方单核处理器对应一个内核线程,双核处理器对应两个内核线程,四核处理器对应四个内核线程。 当初的电脑个别是双核四线程、四核八线程,是采纳超线程技术将一个物理解决外围模仿成两个逻辑解决外围,对应两个内核线程,所以在操作系统中看到的CPU数量是理论物理CPU数量的两倍。然而我的如上图是四核四线程,仿佛没有用这个超线程技术。 超线程技术就是利用非凡的硬件指令,把一个物理芯片模仿成两个逻辑解决外围,让单个处理器都能应用线程级并行计算,进而兼容多线程操作系统和软件,缩小了CPU的闲置工夫,进步的CPU的运行效率。这种超线程技术(如双核四线程)由处理器硬件的决定,同时也须要操作系统的反对能力在计算机中体现进去。 程序个别不会间接去应用内核线程,而是去应用内核线程的一种高级接口——轻量级过程(Light Weight Process,LWP),轻量级过程就是咱们通常意义上所讲的线程(咱们在这称它为用户线程),因为每个轻量级过程都由一个内核线程反对,因而只有先反对内核线程,能力有轻量级过程。用户线程与内核线程的对应关系有三种模型:一对一模型、多对一模型、多对多模型,在这以4个内核线程、3个用户线程为例对三种模型进行阐明。 一对一模型 对于一对一模型来说,一个用户线程就惟一地对应一个内核线程(反过来不肯定成立,一个内核线程不肯定有对应的用户线程)。这样,如果CPU没有采纳超线程技术(如四核四线程的计算机,就如上图展现的我应用的计算机),一个用户线程就惟一地映射到一个物理CPU的线程,线程之间的并发是真正的并发。一对一模型使用户线程具备与内核线程一样的长处,一个线程因某种原因阻塞时其余线程的执行不受影响;此处,一对一模型也能够让多线程程序在多处理器的零碎上有更好的体现。但一对一模型也有两个毛病: 1.许多操作系统限度了内核线程的数量,因而一对一模型会使用户线程的数量受到限制; 2.许多操作系统内核线程调度时,上下文切换的开销较大,导致用户线程的执行效率降落。多对一模型 多对一模型将多个用户线程映射到一个内核线程上,线程之间的切换由用户态的代码来进行,因而绝对一对一模型,多对一模型的线程切换速度要快许多;此外,多对一模型对用户线程的数量简直无限度。但多对一模型也有两个毛病: 1.如果其中一个用户线程阻塞,那么其它所有线程都将无奈执行,因为此时内核线程也随之阻塞了; 2.在多处理器零碎上,处理器数量的减少对多对一模型的线程性能不会有显著的减少,因为所有的用户线程都映射到一个处理器上了。多对多模型 多对多模型联合了一对一模型和多对一模型的长处,将多个用户线程映射到多个内核线程上。多对多模型的长处有: 1.一个用户线程的阻塞不会导致所有线程的阻塞,因为此时还有别的内核线程被调度来执行; 2.多对多模型对用户线程的数量没有限度; ...

December 17, 2020 · 1 min · jiezi

浅析-Linux-进程与线程

简介进程与线程是所有的程序员都熟知的概念,简单来说进程是一个执行中的程序,而线程是进程中的一条执行路径。进程是操作系统中基本的抽象概念,本文介绍 Linux 中进程和线程的用法以及原理,包括创建、消亡等。 进程创建与执行Linux 中进程的创建与执行分为两个函数,分别是 fork 和 exec,如下代码所示: int main() { pid_t pid; if ((pid = fork() < 0) { printf("fork error\n"); } else if (pid == 0) { // child if (execle("/home/work/bin/test1", "test1", NULL) < 0) { printf("exec error\n"); } } // parent if (waitpid(pid, NULL) < 0) { printf("wait error\n"); }}fork 从当前进程创建一个子进程,此函数返回两次,对于父进程而言,返回的是子进程的进程号,对于子进程而言返回 0。子进程是父进程的副本,拥有与父进程一样的数据空间、堆和栈的副本,并且共享代码段。 由于子进程通常是为了调用 exec 装载其它程序执行,所以 Linux 采用了写时拷贝技术,即数据段、堆和栈的副本并不会在 fork 之后就真的拷贝,只是将这些内存区域的访问权限变为只读,如果父子进程中有任一个要修改这些区域,才会修改对应的内存页生成新的副本,这样子是为了提高性能。 fork 之后父进程先执行还是子进程先执行是不确定的,所以如果要求父子进程进行同步,往往需要使用进程间通信。fork 之后子进程会继承父进程的很多东西,如: 打开的文件实际用户 ID、组用户 ID 等进程组当前工作目录信号屏蔽和安排...父子进程的区别在于: ...

July 16, 2019 · 2 min · jiezi

11JavaScript-线程机制与事件机制

JavaScript线程机制与事件机制一、进程与线程进程(process)程序的一次执行,它占有一片独有的内存空间。可以通过windows任务管理器查看进程。线程(thread)是进程内的一个独立执行单元。是程序执行的一个完整流程。是CPU的最小调度单元。进程与线程图解 相关知识应用程序必须运行在某个进程的某个线程上。一个进程中至少有一个运行的线程:主线程,进程启动后自动创建。一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的。一个进程内的数据可以供其中的多个线程中直接共享。多个进程之间的数据是不能直接共享的。线程池(thread pool):保存多个线程对象的容器,实现线程对象的反复利用。相关问题(1)何为多进程与多线程? 多进程运行:一个应用程序可以同时启动多个实例运行。多线程:在一个进程内,同时有多个线程运行。(2)比较单线程与多线程? 多线程 优点:能有效提升CPU的利用率。缺点: 创建多线程开销。线程间切换开销。死锁与状态同步问题。单线程 优点:顺序编程简单易懂。缺点:效率低。(3)JS是单线程还是多线程? JS是单线程运行的。但是使用H5中的 Web Workers可以多线程运行。(4)浏览器运行是单线程还是多线程? 浏览器都是多线程运行的。(5)浏览器运行是单进程还是多进程? 有的是单进程: 老版Firefox老版IE有的是多进程: Chrome新版Firefox新版IE如何查看浏览器是否是多进程运行的呢? 任务管理器==>进程二、浏览器内核(1)浏览器内核是支撑浏览器运行的最核心的程序。 (2)不同的浏览器内核不一样: Chrome,Safari:webkitFirefox:GeckoIE:Trident360,搜狗等国内浏览器:Trident+webkit(3)内核由很多模块组成: 主线程 js引擎模块:负责js程序的编译与运行。html,css文档解析模块:负责页面文本的解析。DOM/CSS模块:负责DOM/CSS在内存中的相关处理。布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)分线程 定时器模块:负责定时器的管理。DOM事件响应模块:负责事件的管理。网络请求模块:负责服务器请求(常规/ajax)。三、定时器引发的思考(1)定时器真是定时执行的吗? 定时器并不能保证真正定时执行。一般会延迟一丁点(可以接受), 也有可能延迟很长时间(不能接受)。<button id="btn">启动定时器</button>document.getElementById('btn').onclick = function () { var start = Date.now() console.log('启动定时器前...') setTimeout(function () { console.log('定时器执行了', Date.now()-start) }, 200) console.log('启动定时器后...')} 给上面回调函数加一个长时间的任务: document.getElementById('btn').onclick = function () { var start = Date.now() console.log('启动定时器前...') setTimeout(function () { console.log('定时器执行了', Date.now()-start) }, 200) console.log('启动定时器后...') // 做一个长时间的工作 for (var i = 0; i < 1000000000; i++) {}}结果: ...

June 27, 2019 · 2 min · jiezi

全栈之路JAVA基础课程三20190614v10

欢迎进入JAVA基础课程 本系列文章将主要针对JAVA一些基础知识点进行讲解,为平时归纳所总结,不管是刚接触JAVA开发菜鸟还是业界资深人士,都希望对广大同行带来一些帮助。若有问题请及时留言或加QQ:243042162。 谨记:最近习大大大力倡导“不忘初心、牢记使命”的主题教育,提出了“守初心、担使命、找差距、抓落实”的总体要求,这正好也映射在了我们个人生活和工作中。人到中年,最多的是懒,缺乏了学生时代的初心,不管你处于哪个年纪,请摆脱借口,端正态度,以家庭为寄托,以未来为展望,将技术提升和家庭教育落到实处,让自己有生之年还能得到质的飞跃。并发和多线程1. 进程和线程 进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。 线程:进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。 区别:进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。 a.创建线程的几种方式(1)继承 Thread(2)实现 Runnable 接口(3)应用程序可以使用 Executor 框架来创建线程池b.线程的几种状态(早上打车去上班)(1)新建(准备叫一辆嘀嘀打车)(2)可运行(找到一辆可以带你去上班的车)(3)运行(司机接到你,带你去上班)(4)阻塞(路上堵车了):等待阻塞-wait、同步阻塞-同步锁、其他阻塞- Thread.sleep(long ms)或 t.join ()方法(5)死亡(到公司了,付钱下车)c.同步方法和同步代码块同步方法默认用 this 或者当前类 class 对象作为锁;同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法。死锁概念:两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是这些线程都陷入了无限的等待中。举例:某计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。产生死锁原因:1. 系统资源的竞争2. 进程推进顺序非法3. 死锁产生的必要条件产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。(1)互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。(2)不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。(3)请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。(4)循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有。 代码块/** * 死锁实例 * t1先运行,这个时候flag==true,先锁定obj1,然后睡眠1秒钟 * 而t1在睡眠的时候,另一个线程t2启动,flag==false,先锁定obj2,然后也睡眠1秒钟 * t1睡眠结束后需要锁定obj2才能继续执行,而此时obj2已被t2锁定 * t2睡眠结束后需要锁定obj1才能继续执行,而此时obj1已被t1锁定 * t1、t2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。 */ class DeadLock implements Runnable{ private static Object obj1 = new Object(); private static Object obj2 = new Object(); private boolean flag; public DeadLock(boolean flag){ this.flag = flag; } @Override public void run(){ System.out.println(Thread.currentThread().getName() + "运行"); if(flag){ synchronized(obj1){ System.out.println(Thread.currentThread().getName() + "已经锁住obj1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(obj2){ // 执行不到这里 System.out.println("1秒钟后,"+Thread.currentThread().getName() + "锁住obj2"); } } }else{ synchronized(obj2){ System.out.println(Thread.currentThread().getName() + "已经锁住obj2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(obj1){ // 执行不到这里 System.out.println("1秒钟后,"+Thread.currentThread().getName() + "锁住obj1"); } } } }}public class LockDemo { public static void main(String[] args) { Thread t1 = new Thread(new DeadLock(true), "线程1"); Thread t2 = new Thread(new DeadLock(false), "线程2"); t1.start(); t2.start(); }}运行结果 ...

June 14, 2019 · 1 min · jiezi

为什么kill进程后socket一直处于FINWAIT1状态

本文介绍一个因为conntrack内核参数设置和iptables规则设置的原因导致TCP连接不能正常关闭(socket一直处于FIN_WAIT_1状态)的案例,并介绍conntrack相关代码在conntrack表项超时后对新报文的处理逻辑。 案例现象问题的现象: ECS上有一个进程,建立了到另一个服务器的socket连接。 kill掉进程,发现tcpdump抓不到FIN包发出,导致服务器端的连接没有正常关闭。为什么有这种现象呢? 梳理正常情况下kill进程后,用户态调用close()系统调用来发起TCP FIN给对端,所以这肯定是个异常现象。关键的信息是: 用户态kill进程。ECS网卡层面没有抓到FIN包。从这个现象描述中可以推断问题出在位于用户空间和网卡驱动中间的内核态中。但是是系统调用问题,还是FIN已经构造后出的问题,还不确定。这时候比较简单有效的判断的方法是看socket的状态。socket处于TIME_WAIT_1状态,这个信息很有用,可以判断系统调用是正常的,因为按照TCP状态机,FIN发出来后socket会进入TIME_WAIT_1状态,在收到对端ACK后进入TIME_WAIT_2状态。关于socket的另一个信息是:这个socket长时间处于TIME_WAIT_1状态,这也反向证明了在网卡上没有抓到FIN包的陈述是合理。FIN包没出虚机网卡,对端收不到FIN,所以自然没有机会回ACK。 真凶问题梳理到了这里,基本上可以进一步聚焦了,在没有大bug的情况下,需要重点看下iptables(netfilter), tc等机制对报文的影响。果然在ECS中有许多iptables规则。利用iptables -nvL可以打出每条rule匹配到的计数,或者利用写log的办法,示例如下: # 记录下new state的报文的日志iptables -A INPUT -p tcp -m state --state NEW -j LOG --log-prefix "[iptables] INPUT NEW: "在这个案例中,通过计数和近一步的log,发现了是OUTPUT chain的最后一跳DROP规则被匹配上了,如下: # iptables -A OUTPUT -m state --state INVALID -j DROP问题的真凶在此时被找到了:iptables规则丢弃了kill进程后发出的FIN包,导致对端收不到,连接无法正常关闭。 到了这里,离最终的root cause还有两个疑问: 问题是否在全局必现?触发的条件是什么?为什么FIN包被认为是INVALID状态?何时触发先来看第一个问题:问题是否在全局必现?触发的条件是什么? 对于ECS上与服务器建立TCP连接的进程,问题实际上不是每次必现的。建议用netcat来做测试,验证下是否是全局影响。通过测试,有如下发现: 利用netcat做类似的操作,也能复现同样的问题,说明这个确实是全局影响,与特定进程或者连接无关。连接时间比较长时能复现,时间比较短时kill进程时能正常发FIN。看下conntrack相关的内核参数设置,发现ECS环境的conntrack参数中有一个显著的调整: net.netfilter.nf_conntrack_tcp_timeout_established = 120这个值默认值是5天,阿里云官网文档推荐的调优值是1200秒,而现在这个ECS环境中的设置是120秒,是一个非常短的值。 看到这里,可以认定是经过nf_conntrack_tcp_timeout_established 120秒后,conntrack中的连接跟踪记录已经被删除,此时对这个连接发起主动的FIN,在netfilter中回被判定成INVALID状态。而客户在iptables filter表的OUTPUT chain中对INVALID连接状态的报文采取的是drop行为,最终导致FIN报文在netfilter filter表OUTPUT chain中被丢弃。 FIN包被认为是INVALID状态?对于一个TCP连接,在conntrack中没有连接跟踪表项,一端FIN掉连接的时候的时候被认为是INVALID状态是很符合逻辑的事情。但是没有发现任何文档清楚地描述这个场景:当用户空间TCP socket仍然存在,但是conntrack表项已经不存在时,对一个“新”的报文,conntrack模块认为它是什么状态。 所有文档描述conntrack的NEW, ESTABLISHED, RELATED, INVALID状态时大同小异,比较详细的描述如文档: The NEW state tells us that the packet is the first packet that we see. This means that the first packet that the conntrack module sees, within a specific connection, will be matched. For example, if we see a SYN packet and it is the first packet in a connection that we see, it will match. However, the packet may as well not be a SYN packet and still be considered NEW. This may lead to certain problems in some instances, but it may also be extremely helpful when we need to pick up lost connections from other firewalls, or when a connection has already timed out, but in reality is not closed.如上对于NEW状态的描述为:conntrack module看见的一个报文就是NEW状态,例如TCP的SYN报文,有时候非SYN也被认为是NEW状态。 ...

June 5, 2019 · 4 min · jiezi

linux进程

进程/线程虚拟地址空间进程/线程先申请虚拟地址空间4G,其中1G的内核空间是所有普通进程/线程共享的。每个在创建的时候在内核栈底部申请一个thread_info,进程通过fork,线程clone(区别是传入一些共享的东西)。thread_info 结构体中有一个 struct task_struct task,task 指向的就是这个进程或线程相关的 task_struct 对象。thread_info 结构体中有一个 struct task_struct task,task 指向的就是这个进程或线程相关的 task_struct 对象。 task_struct描述进程/线程虚拟空间位置信息等。 volatile long state;//进程状态void *stack; //内核栈struct thread_info *thread_info。thread_info 指向该进程/线程的基本信息。struct mm_struct *mm。mm_struct 对象用来管理该进程/线程的页表以及虚拟内存区。struct mm_struct *active_mm。主要用于内核线程访问主内核页全局目录。struct fs_struct *fs。fs_struct 是关于文件系统的对象。struct files_struct *files。files_struct 是关于打开的文件的对象。struct signal_struct *signal。signal_struct 是关于信号的对象。pid_t pid; pid_t tgid; ……整体虚拟内存图示: 栈局部变量、函数参数、返回地址等堆动态分配的内存(有碎片,brk或mmap申请,内存池/垃圾回收)BSS段未初始化或初值为0的全局变量和静态局部变量数据段已初始化且初值非0的全局变量和静态局部变量代码段可执行代码、字符串字面值、只读变量mmap高效的文件I/O方式, 因而被用于装载动态共享库mm_struct: 线程私有:栈,寄存器,线程局部存储TLS(线程级别全局变量,寄存器不会用来存储需要的数据空间很宝贵)。公用:代码,数据,进程空间,打开文件。线程包含了表示进程内执行环境必需的信息,其中包括进程中标示线程的线程ID,一组寄存器值,栈,调度优先级和策略, 信号屏蔽字,errno变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本,程序的全局内存和堆内存,栈以及文件描述符,所以线程的mm_struct *mm指针变量和所属进程的mm指针变量相同。在创建线程的时候,可以通过pthread_attr_t来初始化线程的属性,包括线程的栈布局信息,如栈起始地址stackaddr, 栈大小stacksize。线程栈的空间开辟在所属进程的堆区,线程与其所属的进程共享进程的用户空间,所以线程栈之间可以互访。 fork/exec系统调用系统调用:用户态切换到内核态。用中断实现中断号,中断处理程序 一一对应,维护在中断向量表。中断号有限,一般用一个号(linux 是0x80)表示所有系统调用,再到系统调用表中根据系统调用号获取 forkfork过程:使用fork函数创建的子进程从父进程的继承了全部进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。1.fork创建子进程,首先调用int80中断,然后将系统调用号保存在eax寄存器中,进入内核态后调用do_fork(),2.申请系统堆栈,将子进程pcb插入到队列中(task_struct到内核栈?)3.创建一份父进程的拷贝,他们的内存空间里包含了完全相同的内容,包括当前打开的资源,数据,当然也包含了程序运行到的位置,也就是说fork后子进程也是从fork函数的位置开始往下执行的,而不是从头开始。4.对子进程资源初始化5.为了判别当前正在运行的是哪个进程,fork函数返回了一个pid,在父进程里标识了子进程的id,在子进程里其值为0,在我们的程序里就根据这个值来分开父进程的代码和子进程的代码。写时复制:虽然父子是两份,但只有改时才会变两份 execexec。当前进程替换为新的elf(可执行文件)若想并存: #include <stdio.h>#include <unistd.h>int main(){ if(!fork()) execve("./test",NULL,NULL); else printf("origin process!\n"); return 0;}elf为程序编译链接后的,加载到进程虚拟内存的栈task_struct、各种segment中。进程有自己的虚拟内存 ...

May 24, 2019 · 1 min · jiezi

Java获取当前进程ID以及所有Java进程的进程ID

Java获取当前进程ID以及所有Java进程的进程ID 首先是获取当前Java运行的Java进程ID,这个是网上常见的,也就是Java程序自身将进程ID打印出来:package com.test;import java.lang.management.ManagementFactory;import java.lang.management.RuntimeMXBean;public class Target { public static void main(String[] args) throws InterruptedException { System.out.println(getProcessID()); while(true) { Thread.sleep(10000); } } public static final int getProcessID() { RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); System.out.println(runtimeMXBean.getName()); return Integer.valueOf(runtimeMXBean.getName().split("@")[0]) .intValue(); } }ManagementFactory是一个在运行时管理和监控Java VM的工厂类,它能提供很多管理VM的静态接口,比如RuntimeMXBean;RuntimeMXBean是Java虚拟机的运行时管理接口. 获取所有正在运行着的Java进程package com.test;import java.util.HashSet;import java.util.Set;import sun.jvmstat.monitor.MonitoredHost;import sun.jvmstat.monitor.MonitoredVm;import sun.jvmstat.monitor.MonitoredVmUtil;import sun.jvmstat.monitor.VmIdentifier;public class ProcessID { public static void main(String[] args) throws Exception { // 获取监控主机 MonitoredHost local = MonitoredHost.getMonitoredHost("localhost"); // 取得所有在活动的虚拟机集合 Set<?> vmlist = new HashSet<Object>(local.activeVms()); // 遍历集合,输出PID和进程名 for(Object process : vmlist) { MonitoredVm vm = local.getMonitoredVm(new VmIdentifier("//" + process)); // 获取类名 String processname = MonitoredVmUtil.mainClass(vm, true); System.out.println(process + " ------> " + processname); } }}MonitoredHost等类位于${JAVA_HOME}/lib/tools.jar_运行结果: ...

May 3, 2019 · 2 min · jiezi

运行shell脚本时进程数量变多

写了一个很简单的脚本,用于统计memcache进程的数量: #!/bin/bashecho `ps aux | grep memcache | grep -v grep | wc -l`然而在执行时却遇到了问题: [work@ oss_memcache_status]$ pwd/home/work/cdn/monitor/ocelot-scripts/oss_memcache_status[work@ oss_memcache_status]$ ./run.sh1[work@ oss_memcache_status]$ ../oss_memcache_status/run.sh3这个原因是因为我们在执行shell脚本时,会通过子进程的方式来执行,因此统计数量比预期要多。解决方案为grep -v bash。 执行shell脚本的方式我们有三种常用的方式执行shell脚本: source run.sh: 会在当前进程下执行脚本,执行时的变量会保存下来。. run.sh: 和source方法基本一样,区别在于source不是POSIX要求的。./run.sh: 如果脚本以#!/bin/bash开头,会在单独的子进程中执行,执行完毕后变量不保存。否则和source一样。因此,在上面的脚本中,我们在执行时,因为是以#!/bin/bash开头,会在子进程中执行,我们改动一下脚本看都是哪些进程: #!/bin/bashecho `ps aux | grep memcache | grep -v grep`执行: work 24414 0.0 0.0 108116 1276 pts/0 S+ 15:31 0:00 /bin/bash ../oss_memcache_status/run.shwork 24415 0.0 0.0 108116 612 pts/0 S+ 15:31 0:00 /bin/bash ../oss_memcache_status/run.shwork 30558 0.0 0.0 371236 47096 ? Ssl 2016 15:14 /usr/local/bin/memcached -d -m 256 -u nobody -l localhost -p 11211我们通过结果,可以看出来,第一个进程和第二个进程的父进程相同,他们都属于当前终端启动的进程。前两个分别为执行run脚本的进程和调起的子进程(这两个什么区别,我也不太清楚),第三个为真正的进程。 ...

April 25, 2019 · 1 min · jiezi

swoole进程结构

一、进程的基本知识什么是进程,所谓进程其实就是操作系统中一个正在运行的程序,我们在一个终端当中,通过php,运行一个php文件,这个时候就相当于我们创建了一个进程,这个进程会在系统中驻存,申请属于它自己的内存空间系统资源并且运行相应的程序对于一个进程来说,它的核心内容分为两个部分,一个是它的内存,这个内存是这进程创建之初从系统分配的,它所有创建的变量都会存储在这一片内存环境当中一个是它的上下文环境我们知道进程是运行在操作系统的,那么对于程序来说,它的运行依赖操作系统分配给它的资源,操作系统的一些状态。在操作系统中可以运行多个进程的,对于一个进程来说,它可以创建自己的子进程,那么当我们在一个进程中创建出若干个子进程的时候那么可以看到如图,子进程和父进程一样,拥有自己的内存空间和上下文环境二、Swoole进程结构Swoole的高效不仅仅于底层使用c编写,他的进程结构模型也使其可以高效的处理业务,我们想要深入学习,并且在实际的场景当中使用必须了解,下面我们先看一下结构图:首先先介绍下swoole的这几种进程分别是干什么的:从这些层级的名字,我们先大概说一下,下面这些层级分别是干什么的,做一个详细的说明。Master进程:主进程Manger进程:管理进程Worker进程:工作进程Task进程:异步任务工作进程1、Master进程第一层,Master进程,这个是swoole的主进程,这个进程是用于处理swoole的核心事件驱动的,那么在这个进程当中可以看到它拥有一个MainReactor[线程]以及若干个Reactor[线程],swoole所有对于事件的监听都会在这些线程中实现,比如来自客户端的连接,信号处理等。每一个线程都有自己的用途,下面多每个线程有一个了解MainReactor(主线程)主线程会负责监听server socket,如果有新的连接accept,主线程会评估每个Reactor线程的连接数量。将此连接分配给连接数最少的reactor线程,做一个负载均衡。Reactor线程组Reactor线程负责维护客户端机器的TCP连接、处理网络IO、收发数据完全是异步非阻塞的模式。swoole的主线程在Accept新的连接后,会将这个连接分配给一个固定的Reactor线程,在socket可读时读取数据,并进行协议解析,将请求投递到Worker进程。在socket可写时将数据发送给TCP客户端。心跳包检测线程(HeartbeatCheck)Swoole配置了心跳检测之后,心跳包线程会在固定时间内对所有之前在线的连接发送检测数据包UDP收包线程(UdpRecv)接收并且处理客户端udp数据包2、管理进程ManagerSwoole想要实现最好的性能必须创建出多个工作进程帮助处理任务,但Worker进程就必须fork操作,但是fork操作是不安全的,如果没有管理会出现很多的僵尸进程,进而影响服务器性能,同时worker进程被误杀或者由于程序的原因会异常退出,为了保证服务的稳定性,需要重新创建worker进程。Swoole在运行中会创建一个单独的管理进程,所有的worker进程和task进程都是从管理进程Fork出来的。管理进程会监视所有子进程的退出事件,当worker进程发生致命错误或者运行生命周期结束时,管理进程会回收此进程,并创建新的进程。换句话也就是说,对于worker、task进程的创建、回收等操作全权有“保姆”Manager进程进行管理。再来一张图梳理下Manager进程和Worker/Task进程的关系。3、Worker进程worker 进程属于swoole的主逻辑进程,用户处理客户端的一系列请求,接受由Reactor线程投递的请求数据包,并执行PHP回调函数处理数据生成响应数据并发给Reactor线程,由Reactor线程发送给TCP客户端可以是异步非阻塞模式,也可以是同步阻塞模式4、Task进程taskWorker进程这一进城是swoole提供的异步工作进程,这些进程主要用于处理一些耗时较长的同步任务,在worker进程当中投递过来。三、进程查看及流程梳理当启动一个Swoole应用时,一共会创建2 + n + m个进程,2为一个Master进程和一个Manager进程,其中n为Worker进程数。m为TaskWorker进程数。默认如果不设置,swoole底层会根据当前机器有多少CPU核数,启动对应数量的Reactor线程和Worker进程。我机器为1核的。Worker为1。所以现在默认我启动了1个Master进程,1个Manager进程,和1个worker进程,TaskWorker没有设置也就是为0,当前server会产生3个进程。在启动了server之后,在命令行查看当前产生的进程这三个进程中,所有进程的根进程,也就是例子中的2123进程,就是所谓的Master进程;而2212进程,则是Manager进程;最后的2321进程,是Worker进程。client跟server的交互1、client请求到达 Main Reactor,Client实际上是与Master进程中的某个Reactor线程发生了连接。2、Main Reactor根据Reactor的情况,将请求注册给对应的Reactor (每个Reactor都有epoll。用来监听客户端的变化) 3、客户端有变化时Reactor将数据交给worker来处理4、worker处理完毕,通过进程间通信(比如管道、共享内存、消息队列)发给对应的reactor。 5、reactor将响应结果发给相应的连接请求处理完成示意图:后续准备本文是在自己学习Swoole接触到的一些知识,在初步整理后发送出来,希望能与大家一起学习,文章不足等问题大家可以一起讨论学习,欢迎骚扰~~。后面准备从网络模型入手更好的理解swoole的实现原理,比较与传统PHP-FPM工作模式的问题,之前出过一篇关于(一)如何实现一个单进程阻塞的网络服务器大家可以先了解下,如何一步步演变为多进程master-worker模型。欢迎大家指正文章问题~

April 13, 2019 · 1 min · jiezi

进程与线程

进程的定义一个进程实体包括三部分:程序段,数据段,pcb,这个进程实体就简称为进程进程的特征动态性进程由创建而产生,由调度而执行,由撤销而消亡有一定生命期进程的实质是进程实体的执行过程并发性指多个进程实体能够同时存在内存中,且能够在一段时间内同时运行。这也正是引入进程概念的目的异步性每个进程都是按各自独立的、不可预知的速度向前推进正是由于有异步性,os 才会引进相应的同步机制,才能保证进程并发执行的结果是可再现的独立性进程是独立运行,独立接受调度的基本单位进程的基本状态及转换创建、就绪、执行、阻塞、终止切记,创建完并非立即执行,而是就绪状态,当 CPU 调度时才转变为执行状态执行->就绪,是由于时间片用完,让出 CPU 资源,变回就绪状态就绪状态:指进程已经处于准备好运行的状态,那具体什么是准备好呢?即进程已分配到除 CPU 以外的所有必要资源后,只要再获得 CPU,便可立即执行创建、静止就绪、活动就绪、执行、静止阻塞、活动阻塞、终止创建 -> 活动就绪:在当前系统的性能和内存的容量均允许的情况下,完成对进程创建的必要操作后,进程便为活动就绪状态创建 -> 静止就绪:不分配给新建进程所需资源,主要是主存,所以该进程被安置在外存,不参与调度,状态转为静止就绪状态操作系统内核定义将一些与硬件紧密联系的模块(如中断处理程序等)、各种常用设备的驱动程序、运行频率较高的模块(如时钟管理,进程调度),都安排在紧靠硬件的软件层次中,将它们常驻内存,即通常称为 OS 内核。目的或者有点有两个:对这些软件进行保护,防止遭受其他应用程序的破坏提高 OS 的运行效率(因为常驻内存)OS 内核两大功能:支撑功能中断处理内核最基本的功能各种类型的系统调用、键盘命令的输入、进程调度、设备驱动等,都依赖于中断时钟管理内核的一项基本功能例如时间片轮转调度中,每当时间片用完时,便由时钟管理产生一个中断信号,促使调度程序重新进行调度实时系统中的截止时间截止、批处理系统中的最长运行时间控制等也依赖于时钟管理原语操作所谓原语,就是由若干条指令组成的,用于完成一定功能的一个过程。原子操作是指一个操作中的所有动作要么全做,要么全不做。它是一个不可分割的基本单位。资源管理功能,包括进程管理、 存储器管理、设备管理进程和线程的区别从调度性、并发性、系统开销、拥有资源、独立性、支持多处理机系统方面比较调度性线程是调度的基本单位,是能独立运行的基本单位,线程切换代价低只有从一个进程中的线程切换到另一个进程中的线程时,才会引起进程的切换拥有资源进程是拥有资源的基本单位,而线程仅有一点必不可少,能保证独立运行的资源而已那线程拥有哪些必不可少的资源呢?如线程控制块 TCB,程序计数器,保留局部变量,少数状态参数和返回地址等的一组寄存器和堆栈多个线程共享所属进程拥有的资源并发性进程之间可以并发执行,一个进程中的多个线程之间也可以并发执行独立性同一进程下的线程之间的独立性低,不同进程之间的独立性高每个进程都拥有一个独立的地址空间和其他资源,除了共享全局变量外,不允许其他进程的访问同一进程的不同线程,共享进程的内存地址空间和资源,如一个线程的堆栈可以被其他线程读写系统开销创建和撤销进程,系统要为之分配和回收 PCB 以及其他资源如内存空间和 I/O 设备而线程由于共享进程所拥有的资源,所以线程的创建、撤销快,上下文切换也快线程之间的同步和通信也比进程的简单多处理机系统支持传统的进程,即单线程进程,无论有多少处理机,该进程只能运行在一个处理机上但对于多线程进程,就可以将一个进程中的多个线程分配到多个处理机上,并行执行多进程和多线程的应用场景首先,多进程单线程模型,简称为多进程;单进程多线程模型,简称为多线程。多进程模型优点编程相对容易;通常不需要考虑锁和同步资源的问题。更强的容错性:比起多线程的一个好处是一个进程崩溃了不会影响其他进程。有内核保证的隔离:数据和错误隔离。对于使用如C/C++这些语言编写的本地代码,错误隔离是非常有用的采用多进程架构的程序一般可以做到一定程度的自恢复(master守护进程监控所有worker进程,发现进程挂掉后将其重启)案例nginx主流的工作模式是多进程模式(也支持多线程模型)几乎所有的web server服务器服务都有多进程的,至少有一个守护进程配合一个worker进程,例如apached,httpd等等以d结尾的进程包括init.d本身就是0级总进程,所有你认知的进程都是它的子进程;chrome浏览器也是多进程方式。redis也可以归类到“多进程单线程”模型(平时工作是单个进程,涉及到耗时操作如持久化或aof重写时会用到多个进程)多线程模型优点创建速度快,方便高效的数据共享多个线程之间可以共享相同的地址空间适用场景线程间有数据共享,并且数据是需要修改的(不同任务间需要大量共享数据或频繁通信时);提供非均质的服务(有优先级任务处理)事件响应有优先级;单任务并行计算,在非CPU Bound的场景下提高响应速度,降低时延;与人有IO交互的应用,良好的用户体验(键盘鼠标的输入,立刻响应)如何选用单进程多线程和多进程单线程,2种模式如何取舍?进程线程间创建的开销不足以作为选择的依据,因为一般我们都是使用线程池或者进程池,在系统启动时就创建了固定的线程或进程,不会频繁的创建和销毁;首先,根据工作集(需要共享的内存)的大小来定;如果工作集较大,就用多线程,避免cpu cache频繁的换入换出;比如memcached缓存系统;其次,选择的依据根据以上多线程适用的场景来对比自身的业务场景,是否有这样场景需求:数据共享、提供非均质的服务,单任务拆散并行化等;如果没有必要,或者多进程就可以很好的胜任,就多用多进程,享受单线程编程带来便利;RCU的发明者,Paul McKenny 在《Is Parallel Programming Hard, And, If So, What Can You Do About It?》说过: 能用多进程方便的解决问题的时候不要使用多线程。引用出处https://blog.csdn.net/PirLCK/…汤小丹-计算机操作系统(第四版)

March 15, 2019 · 1 min · jiezi

php中pcntl_fork创建子进程

一、php中pcntl_fork函数概述pcntl_fork()函数是php中用于创建子进程的一个函数,返回创建的子进程的pid。该函数创建子进程具体fork的过程:(1)调用该函数即创建一个子进程,创建成功父进程返回子进程的pid,子进程返回0;(2)创建子进程实际上对父进程的一个拷贝,共享代码空间,拷贝父进程的数据,也就是说父进程改变父进程的数据,子进程改变子进程的数据变量等;二、示例代码分析代码示例:<?php $curr_pid = posix_getpid();//获取当前的进程id //将当前进程的id写入文件中 echo ‘当前进程:’.$curr_pid.PHP_EOL; //开始创建子进程 $son_pid = pcntl_fork();//返回子进程的id //查看当前进程 echo ‘创建子进程之后当前的进程为:’.posix_getpid().PHP_EOL; //创建了子进程之后 if($son_pid > 0){ echo ‘子进程id:’.$son_pid.PHP_EOL; }以上代码执行后结果为:示例代码分析:(1)发现创建了子进程之后,系统会切换到子进程中,而子进程中的代码是从含有pcntl_fork函数的那行执行的(2)创建子进程之后,子进程的代码段是拷贝pcntl_fork函数及之后的代码段,之前的代码段并不拷贝,但是具体的数据变量子进程仍然会拷贝(3)可见,fork之后程序会分叉执行,即子进程执行三、pcntl_fork的业务场景举例php的多进程中,常用pcntl_fork来实现并发,多用于一些简单工具的实现。例如监控工具,想要监控几个不同指标的情形,可以使用主进程监控各指标的配置变化,然后对每个指标分别fork一个子进程来监控其具体的情形,当主进程发现指标的配置改变则kill掉之前的子进程重新创建子进程进行监控。主进程进行业务分发操作,子进程进行具体的业务逻辑执行。

March 13, 2019 · 1 min · jiezi

Android(IPC)进程间通讯1:详解Binder由来?

Android开发的进程间通讯,整个Android的应用都依赖于binder做底层通信机制。而Linux中提供的进程间通讯方式并没有binder机制,那么android中为什么要单独创造这种通讯方式呢?带着这个问题,继续往下读。 Linux中进程相关概念 Linux将系统内存划分成了 用户空间 和 内核空间 两部分: 用户空间 : 普通应用程序则运行在用户空间上,它们不能使用某些特定的系统功能,不能直接访问硬件,不能直接访问内核空间。内核空间 : 系统的核心软件会运行在较高的特权级别上,它们驻留在被保护的内存空间上,拥有访问硬件设备的所有权限。 用户程序只能运行在用户空间,用户空间访问内核空间的唯一方式就是系统调用。 linux的用户程序和进程 在linux中,所有的用户程序执行时状态都是进程。进程间存在父子关系来表示同一个用户程序开启的多个同步任务。 所有的进程构成一个以 init 为根的树状结构,这是因为 Linux 内核 并不提供直接建立新进程的系统调用。剩下的所有进程都是 init 进程通过 fork 机制建立的。新的进程要通过老的进程复制自身得到,这就是 fork。fork 是一个系统调用。 ...

February 22, 2019 · 1 min · jiezi

Java多线程001——一图读懂线程与进程

本博客 猫叔的博客,转载请申明出处前言本系列将由浅入深,学习Java并发多线程。一图读懂线程与进程1、一个进程可以包含一个或多个线程。(其实你经常听到“多线程”,没有听过“多进程”嘛)2、进程存在堆和方法区3、线程存在程序计数器和栈4、堆占最大内存,其为创建时分配的,是多线程共享的,主要存放new创建的对象5、方法区也是多线程共享的,主要存放类、常量、静态变量6、CPU的基本执行单位是线程(注意!不是进程)7、由此,线程需要一个程序计数器记录当前线程要执行的指令地址8、当CPU的时间片用完,让出后记录当前执行地址,下次继续执行(时间片轮询)9、只有执行Java代码时pc技数器记录的才是下一条指令的地址,执行native方法,则记录的是undefined地址10、线程中的栈,只要存储线程局部变量、调用栈帧栈帧:C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。公众号:Java猫说现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。

February 19, 2019 · 1 min · jiezi

这一次,真正明白进程与线程

引言作为软件工程师,进程与线程应该是我们必备的知识了,从年年各大企业的面试题就能看出来!必考题:进程与线程的区别小生本学期学习了操作系统这门课,最大的收获就是学会了这道“必考题”。最开始觉得自己学明白了,自己写本文的时候,才觉得这里牵扯到好多东西。操作系统什么是操作系统?说到操作系统,大家一定都不会陌生,我们用的手机内置了Android/iOS操作系统,我的开发用的电脑,Mac OS、Linux、Windows等操作系统。操作系统的非官方描述:操作系统是管理计算机硬件与软件资源的系统软件。发展史想真正明白进程与线程,我们需要了解一下计算机操作系统的历史。手工操作阶段最原始的时候,计算机没有操作系统,只有单独的机器,像下面这样:以一个现代人的眼光来看:没有操作系统,这个机器怎么用啊?反正给我一堆硬件,我是不能让这些硬件跑起来。前人们也是非常厉害的。他们使用记录有程序和数据的卡片(前期用卡片,后来逐渐发展为使用打孔纸带)去操作机器。程序读入机器后,机器就开始工作直到程序停止。据说图灵能非常熟练地用这种方法操作Manchester Mark I机器。缺点用户独占全机CPU速度快,需等待人工操作,资源利用率低批处理操作系统为了解决人工操作速度远慢于CPU的问题,引入了脱机输入/输出系统。计算机能够自动地、成批地处理一个或多个用户的作业,这就不需要手工输入了,这就快了。单道批处理操作系统每次只加载一道作业到内存中执行。多道批处理操作系统每次加载多道作业到内存中并发执行,各个作业轮流使用处理机和其他系统资源,最终依次完成。在内存中同时存放多道相互独立的程序,这些程序共享系统资源,在操作系统的控制下交替在CPU上执行。以后发展的操作系统,我们就不需要知道了,了解到这就足够了。进程与线程程序并发执行程序并发执行时,多道程序共享系统资源,此时,程序的运行失去了封闭性。程序在并发执行时,是多个程序共享系统中的各种资源,因而这些资源的状态将由多个程序来改变,致使程序的运行失去了封闭性。这样,某程序在执行时,必然会受到其它程序的影响。当多个程序并发执行时,我们期望的是运行变快,增加系统吞吐量。但是,就如该图一样,某些程序必须在某些程序执行之后才可继续执行,不能让这个程序想执行就执行,我们要对其进行控制。为了更方便地进行控制,我们就引入了进程的概念。进程在早期,进程是资源分配与调度的基本单位。引入进程,我们发现解决了上述的问题,可以对I1、I2等等都建立进程,并按我们想法的前趋图对进程执行进行约束,达到我们预期的效果。然后程序对我们的进行进行调度,以达到并发执行的效果。然后怎么控制就是PV操作,信号量,进程同步与互斥,这就不属于本文的范围了,大家可以自行学习。进程不足又过了好多年,人们发现最初的进程设计得有些缺点。原始进程两特点:资源分配的基本单位。调度的基本单位。所以,在仅有进程的系统中,想实现并发,需要进行下列操作:创建进程:创建进程PCB,并为它分配资源。撤销进程:回收系统资源,撤销PCB。进程切换:从一个进程环境切换到另一个进程环境。还是这个图为例,我需要创建12个进程,然后进行进程控制,让它们并发执行。创建进程12次,分配12次资源,分配资源消耗了大量的资源。因为进程间的资源相互独立,所以进程间切换是资源的切换,开销相当大。程序结束时,撤销12次资源,回收资源时也有很大的开销。我们有没有一种方法,可以减少创建、撤销以及切换的开销呢?然后,引入了线程的概念。线程进程是携带资源的,切换时开销太大,所以我们需要将资源分配与调度分开来。在现代操作系统中,进程是资源分配的基本单位,线程是调度的基本单位。一个进程中有多个线程,多个线程共享该进程的资源,CPU直接调度进程。还是这张图,如果我们创建1个进程,12个线程会怎么样?程序运行开始,创建一个进程,分配资源1次。创建12个线程,注意,这里是在进程内创建线程,所以,线程无需分配资源,直接使用其进程的资源即可,这开销就非常少了。12个线程并发执行,这12个线程是共享一块系统资源,所以在线程切换时,是不需要切换资源的,开销大大减少。撤销时呢?撤销线程12次,撤销进程,回收资源1次。总结所以,这就是进程与线程的发展,了解了这些历史,我想那些答案再也不用背了吧?CPU的一个核心只能执行一个线程,多个核心可以并发执行,但线程并行数最多不超过核心数。4核的,在不添加新技术的情况下,最多并行4个线程。嗯?Core i7不是4核8线程吗?为什么英特尔的4个核心能跑8个线程?这就是英特尔大名鼎鼎的超线程技术,它将处理机核心中未利用的资源利用起来,再模拟出一个核心,虽然是4核心的处理机,所以在用户看来,该CPU有8个逻辑核心。在桌面端很有用,但是到了服务器端,超线程技术会有些问题。这只是我看了很多篇关于服务器端批判超线程的运维博客之后的看法,待日后,我们再对该技术进行讨论。

January 22, 2019 · 1 min · jiezi

PHP socket初探 --- 先从一个简单的socket服务器开始

[原文地址:https://blog.ti-node.com/blog…]socket的中文名字叫做套接字,这种东西就是对TCP/IP的“封装”。现实中的网络实际上只有四层而已,从上至下分别是应用层、传输层、网络层、数据链路层。最常用的http协议则是属于应用层的协议,而socket,可以简单粗暴的理解为是传输层的一种东西。如果还是很难理解,那再粗暴地点儿tcp://218.221.11.23:9999,看到没?这就是一个tcp socket。socket赋予了我们操控传输层和网络层的能力,从而得到更强的性能和更高的效率,socket编程是解决高并发网络服务器的最常用解决和成熟的解决方案。任何一名服务器程序员都应当掌握socket编程相关技能。在php中,可以操控socket的函数一共有两套,一套是socket_系列的函数,另一套是stream_系列的函数。socket_是php直接将C语言中的socket抄了过来得到的实现,而stream_系则是php使用流的概念将其进行了一层封装。下面用socket_*系函数简单为这一系列文章开个篇。先来做个最简单socket服务器:<?php$host = ‘0.0.0.0’;$port = 9999;// 创建一个tcp socket$listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );// 将socket bind到IP:port上socket_bind( $listen_socket, $host, $port );// 开始监听socketsocket_listen( $listen_socket );// 进入while循环,不用担心死循环死机,因为程序将会阻塞在下面的socket_accept()函数上while( true ){ // 此处将会阻塞住,一直到有客户端来连接服务器。阻塞状态的进程是不会占据CPU的 // 所以你不用担心while循环会将机器拖垮,不会的 $connection_socket = socket_accept( $listen_socket ); // 向客户端发送一个helloworld $msg = “helloworldrn”; socket_write( $connection_socket, $msg, strlen( $msg ) ); socket_close( $connection_socket );}socket_close( $listen_socket );将文件保存为server.php,然后执行php server.php运行起来。客户端我们使用telnet就可以了,打开另外一个终端执行telnet 127.0.0.1 9999按下回车即可。运行结果如下:简单解析一下上述代码来说明一下tcp socket服务器的流程:1.首先,根据协议族(或地址族)、套接字类型以及具体的的某个协议来创建一个socket。2.第二,将上一步创建好的socket绑定(bind)到一个ip:port上。3.第三,开启监听linten。4.第四,使服务器代码进入无限循环不退出,当没有客户端连接时,程序阻塞在accept上,有连接进来时才会往下执行,然后再次循环下去,为客户端提供持久服务。上面这个案例中,有两个很大的缺陷:1.一次只可以为一个客户端提供服务,如果正在为第一个客户端发送helloworld期间有第二个客户端来连接,那么第二个客户端就必须要等待片刻才行。2.很容易受到攻击,造成拒绝服务。分析了上述问题后,又联想到了前面说的多进程,那我们可以在accpet到一个请求后就fork一个子进程来处理这个客户端的请求,这样当accept了第二个客户端后再fork一个子进程来处理第二个客户端的请求,这样问题不就解决了吗?OK!撸一把代码演示一下:<?php$host = ‘0.0.0.0’;$port = 9999;// 创建一个tcp socket$listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );// 将socket bind到IP:port上socket_bind( $listen_socket, $host, $port );// 开始监听socketsocket_listen( $listen_socket );// 进入while循环,不用担心死循环死机,因为程序将会阻塞在下面的socket_accept()函数上while( true ){ // 此处将会阻塞住,一直到有客户端来连接服务器。阻塞状态的进程是不会占据CPU的 // 所以你不用担心while循环会将机器拖垮,不会的 $connection_socket = socket_accept( $listen_socket ); // 当accept了新的客户端连接后,就fork出一个子进程专门处理 $pid = pcntl_fork(); // 在子进程中处理当前连接的请求业务 if( 0 == $pid ){ // 向客户端发送一个helloworld $msg = “helloworldrn”; socket_write( $connection_socket, $msg, strlen( $msg ) ); // 休眠5秒钟,可以用来观察时候可以同时为多个客户端提供服务 echo time().’ : a new client’.PHP_EOL; sleep( 5 ); socket_close( $connection_socket ); exit; }}socket_close( $listen_socket );将代码保存为server.php,然后执行php server.php,客户端依然使用telnet 127.0.0.1 9999,只不过这次我们开启两个终端来执行telnet。重点观察当第一个客户端连接上去后,第二个客户端时候也可以连接上去。运行结果如下:通过接受到客户端请求的时间戳可以看到现在服务器可以同时为N个客户端服务的。但是,接着想,如果先后有1万个客户端来请求呢?这个时候服务器会fork出1万个子进程来处理每个客户端连接,这是会死人的。fork本身就是一个很浪费系统资源的系统调用,1W次fork足以让系统崩溃,即便当下系统承受住了1W次fork,那么fork出来的这1W个子进程也够系统内存喝一壶了,最后是好不容易费劲fork出来的子进程在处理完毕当前客户端后又被关闭了,下次请求还要重新fork,这本身就是一种浪费,不符合社会主义主流价值观。如果是有人恶意攻击,那么系统fork的数量还会呈直线上涨一直到系统崩溃。所以,我们就再次提出增进型解决方案。我们可以预估一下业务量,然后在服务启动的时候就fork出固定数量的子进程,每个子进程处于无限循环中并阻塞在accept上,当有客户端连接挤进来就处理客户请求,当处理完成后仅仅关闭连接但本身并不销毁,而是继续等待下一个客户端的请求。这样,不仅避免了进程反复fork销毁巨大资源浪费,而且通过固定数量的子进程来保护系统不会因无限fork而崩溃。<?php$host = ‘0.0.0.0’;$port = 9999;// 创建一个tcp socket$listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );// 将socket bind到IP:port上socket_bind( $listen_socket, $host, $port );// 开始监听socketsocket_listen( $listen_socket );// 给主进程换个名字cli_set_process_title( ‘phpserver master process’ );// 按照数量fork出固定个数子进程for( $i = 1; $i <= 10; $i++ ){ $pid = pcntl_fork(); if( 0 == $pid ){ cli_set_process_title( ‘phpserver worker process’ ); while( true ){ $conn_socket = socket_accept( $listen_socket ); $msg = “helloworldrn”; socket_write( $conn_socket, $msg, strlen( $msg ) ); socket_close( $conn_socket ); } }}// 主进程不可以退出,代码演示比较粗暴,为了不保证退出直接走while循环,休眠一秒钟// 实际上,主进程真正该做的应该是收集子进程pid,监控各个子进程的状态等等while( true ){ sleep( 1 );}socket_close( $connection_socket );将文件保存为server.php后php server.php执行,然后再用ps -ef | grep phpserver | grep -v grep来看下服务器进程状态:可以看到master进程存在,除此之外还有10个子进程处于等待服务状态,再同一个时刻可以同时为10个客户端提供服务。我们通过telnet 127.0.0.1 9999来尝试一下,运行结果如下图:好啦,php新的征程系列就先通过一个简单的入门开始啦!下篇将会讲述一些比较深刻的理论基础知识。[原文地址:https://blog.ti-node.com/blog…] ...

September 1, 2018 · 2 min · jiezi